Merge pull request #2457 from dv1/combov2-fixes-001
combov2: Fixes for the refresh button, unsafe casts, crashes due to Bluetooth, improved warning screen handling, and additional language support
This commit is contained in:
commit
294f3cb789
19 changed files with 4768 additions and 1544 deletions
|
@ -9,6 +9,8 @@ import info.nightscout.interfaces.userEntry.ValueWithUnitMapper
|
|||
|
||||
interface UserEntryLogger {
|
||||
|
||||
fun log(action: Action, source: Sources, note: String?, timestamp: Long, vararg listValues: ValueWithUnit?)
|
||||
fun log(action: Action, source: Sources, note: String?, timestamp: Long, listValues: List<ValueWithUnit?>)
|
||||
fun log(action: Action, source: Sources, note: String? = "", vararg listValues: ValueWithUnit?)
|
||||
fun log(action: Action, source: Sources, vararg listValues: ValueWithUnit?)
|
||||
fun log(action: Action, source: Sources, note: String? = "", listValues: List<ValueWithUnit?> = listOf())
|
||||
|
|
|
@ -133,6 +133,7 @@ open class Notification {
|
|||
const val EOELOW_PATCH_ALERTS = 79
|
||||
const val COMBO_PUMP_SUSPENDED = 80
|
||||
const val COMBO_UNKNOWN_TBR = 81
|
||||
const val BLUETOOTH_NOT_ENABLED = 82
|
||||
|
||||
const val USER_MESSAGE = 1000
|
||||
|
||||
|
|
|
@ -29,16 +29,14 @@ class UserEntryLoggerImpl @Inject constructor(
|
|||
|
||||
private val compositeDisposable = CompositeDisposable()
|
||||
|
||||
override fun log(action: Action, source: Sources, note: String?, vararg listValues: ValueWithUnit?) = log(action, source, note, listValues.toList())
|
||||
override fun log(action: Action, source: Sources, note: String?, timestamp: Long, vararg listValues: ValueWithUnit?) = log(action, source, note, timestamp, listValues.toList())
|
||||
|
||||
override fun log(action: Action, source: Sources, vararg listValues: ValueWithUnit?) = log(action, source, "", listValues.toList())
|
||||
|
||||
override fun log(action: Action, source: Sources, note: String?, listValues: List<ValueWithUnit?>) {
|
||||
override fun log(action: Action, source: Sources, note: String?, timestamp: Long, listValues: List<ValueWithUnit?>) {
|
||||
val filteredValues = listValues.toList().filterNotNull()
|
||||
log(
|
||||
listOf(
|
||||
UserEntry(
|
||||
timestamp = dateUtil.now(),
|
||||
timestamp = timestamp,
|
||||
action = action,
|
||||
source = source,
|
||||
note = note ?: "",
|
||||
|
@ -48,6 +46,12 @@ class UserEntryLoggerImpl @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
override fun log(action: Action, source: Sources, note: String?, vararg listValues: ValueWithUnit?) = log(action, source, note, listValues.toList())
|
||||
|
||||
override fun log(action: Action, source: Sources, vararg listValues: ValueWithUnit?) = log(action, source, "", listValues.toList())
|
||||
|
||||
override fun log(action: Action, source: Sources, note: String?, listValues: List<ValueWithUnit?>) = log(action, source, note, dateUtil.now(), listValues)
|
||||
|
||||
override fun log(entries: List<UserEntry>) {
|
||||
compositeDisposable += repository.runTransactionForResult(UserEntryTransaction(entries))
|
||||
.subscribeOn(aapsSchedulers.io)
|
||||
|
|
|
@ -273,7 +273,13 @@ class PumpSyncImplementation @Inject constructor(
|
|||
pumpSerial = pumpSerial
|
||||
)
|
||||
)
|
||||
uel.log(UserEntry.Action.CAREPORTAL, pumpType.source.toDbSource(), note, ValueWithUnit.Timestamp(timestamp), ValueWithUnit.TherapyEventType(type.toDBbEventType()))
|
||||
uel.log(
|
||||
action = UserEntry.Action.CAREPORTAL,
|
||||
source = pumpType.source.toDbSource(),
|
||||
note = note,
|
||||
timestamp = timestamp,
|
||||
ValueWithUnit.Timestamp(timestamp), ValueWithUnit.TherapyEventType(type.toDBbEventType())
|
||||
)
|
||||
repository.runTransactionForResult(InsertIfNewByTimestampTherapyEventTransaction(therapyEvent))
|
||||
.doOnError {
|
||||
aapsLogger.error(LTag.DATABASE, "Error while saving TherapyEvent", it)
|
||||
|
|
|
@ -7,8 +7,10 @@ import android.content.Intent
|
|||
import android.content.IntentFilter
|
||||
import info.nightscout.comboctl.base.BluetoothAddress
|
||||
import info.nightscout.comboctl.base.BluetoothDevice
|
||||
import info.nightscout.comboctl.base.BluetoothNotEnabledException
|
||||
import info.nightscout.comboctl.base.BluetoothException
|
||||
import info.nightscout.comboctl.base.BluetoothInterface
|
||||
import info.nightscout.comboctl.base.BluetoothNotAvailableException
|
||||
import info.nightscout.comboctl.base.LogLevel
|
||||
import info.nightscout.comboctl.base.Logger
|
||||
import info.nightscout.comboctl.base.toBluetoothAddress
|
||||
|
@ -35,7 +37,10 @@ private val logger = Logger.get("AndroidBluetoothInterface")
|
|||
* instance is an ideal choice.
|
||||
*/
|
||||
class AndroidBluetoothInterface(private val androidContext: Context) : BluetoothInterface {
|
||||
private var bluetoothAdapter: SystemBluetoothAdapter? = null
|
||||
private var _bluetoothAdapter: SystemBluetoothAdapter? = null
|
||||
private val bluetoothAdapter: SystemBluetoothAdapter
|
||||
get() = _bluetoothAdapter ?: throw BluetoothNotAvailableException()
|
||||
|
||||
private var rfcommServerSocket: SystemBluetoothServerSocket? = null
|
||||
private var discoveryStarted = false
|
||||
private var discoveryBroadcastReceiver: BroadcastReceiver? = null
|
||||
|
@ -96,11 +101,16 @@ class AndroidBluetoothInterface(private val androidContext: Context) : Bluetooth
|
|||
else @Suppress("DEPRECATION") getParcelableExtra(name)
|
||||
|
||||
fun setup() {
|
||||
val bluetoothManager = androidContext.getSystemService(Context.BLUETOOTH_SERVICE) as SystemBluetoothManager
|
||||
bluetoothAdapter = bluetoothManager.adapter
|
||||
val bluetoothManager = androidContext.getSystemService(Context.BLUETOOTH_SERVICE) as? SystemBluetoothManager
|
||||
_bluetoothAdapter = bluetoothManager?.adapter
|
||||
|
||||
checkIfBluetoothEnabledAndAvailable()
|
||||
|
||||
val bondedDevices = checkForConnectPermission(androidContext) {
|
||||
bluetoothAdapter!!.bondedDevices
|
||||
// The "not enabled" check above is important, because in the disabled
|
||||
// state, the adapter returns an empty list here. This would mislead
|
||||
// the logic below into thinking that there are no bonded devices.
|
||||
bluetoothAdapter.bondedDevices
|
||||
}
|
||||
|
||||
logger(LogLevel.DEBUG) { "Found ${bondedDevices.size} bonded Bluetooth device(s)" }
|
||||
|
@ -180,7 +190,7 @@ class AndroidBluetoothInterface(private val androidContext: Context) : Bluetooth
|
|||
// necessary for correct function, just a detail for sake of completeness.)
|
||||
logger(LogLevel.DEBUG) { "Setting up RFCOMM listener socket" }
|
||||
rfcommServerSocket = checkForConnectPermission(androidContext) {
|
||||
bluetoothAdapter!!.listenUsingInsecureRfcommWithServiceRecord(
|
||||
bluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||
sdpServiceName,
|
||||
Constants.sdpSerialPortUUID
|
||||
)
|
||||
|
@ -203,7 +213,7 @@ class AndroidBluetoothInterface(private val androidContext: Context) : Bluetooth
|
|||
logger(LogLevel.DEBUG) { "Closing accepted incoming RFCOMM socket" }
|
||||
try {
|
||||
socket.close()
|
||||
} catch (e: IOException) {
|
||||
} catch (_: IOException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -274,20 +284,24 @@ class AndroidBluetoothInterface(private val androidContext: Context) : Bluetooth
|
|||
stopDiscoveryInternal()
|
||||
}
|
||||
|
||||
override fun getDevice(deviceAddress: BluetoothAddress): BluetoothDevice =
|
||||
AndroidBluetoothDevice(androidContext, bluetoothAdapter!!, deviceAddress)
|
||||
override fun getDevice(deviceAddress: BluetoothAddress): BluetoothDevice {
|
||||
checkIfBluetoothEnabledAndAvailable()
|
||||
return AndroidBluetoothDevice(androidContext, bluetoothAdapter, deviceAddress)
|
||||
}
|
||||
|
||||
override fun getAdapterFriendlyName() =
|
||||
checkForConnectPermission(androidContext) { bluetoothAdapter!!.name }
|
||||
checkForConnectPermission(androidContext) { bluetoothAdapter.name }
|
||||
?: throw BluetoothException("Could not get Bluetooth adapter friendly name")
|
||||
|
||||
override fun getPairedDeviceAddresses(): Set<BluetoothAddress> =
|
||||
try {
|
||||
override fun getPairedDeviceAddresses(): Set<BluetoothAddress> {
|
||||
checkIfBluetoothEnabledAndAvailable()
|
||||
return try {
|
||||
deviceAddressLock.lock()
|
||||
pairedDeviceAddresses.filter { pairedDeviceAddress -> deviceFilterCallback(pairedDeviceAddress) }.toSet()
|
||||
} finally {
|
||||
deviceAddressLock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopDiscoveryInternal() {
|
||||
// Close the server socket. This frees RFCOMM resources and ends
|
||||
|
@ -332,6 +346,16 @@ class AndroidBluetoothInterface(private val androidContext: Context) : Bluetooth
|
|||
}
|
||||
}
|
||||
|
||||
private fun checkIfBluetoothEnabledAndAvailable() {
|
||||
// Trying to access bluetoothAdapter here if it is currently null will
|
||||
// automatically cause BluetoothNotAvailableException to be thrown,
|
||||
// so that case is also covered implicitly by this code.
|
||||
if (!bluetoothAdapter.isEnabled || (bluetoothAdapter.state != SystemBluetoothAdapter.STATE_ON)) {
|
||||
logger(LogLevel.ERROR) { "Bluetooth is not enabled" }
|
||||
throw BluetoothNotEnabledException()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onAclConnected(intent: Intent, foundNewPairedDevice: (deviceAddress: BluetoothAddress) -> Unit) {
|
||||
// Sanity check in case we get this notification for the
|
||||
// device already and need to avoid duplicate processing.
|
||||
|
|
|
@ -23,3 +23,19 @@ open class BluetoothPermissionException(message: String?, cause: Throwable?) : B
|
|||
constructor(message: String) : this(message, null)
|
||||
constructor(cause: Throwable) : this(null, cause)
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown when trying to use Bluetooth even though the adapter is not enabled.
|
||||
*
|
||||
* Note that unlike [BluetoothNotAvailableException], here, the adapter _does_ exist,
|
||||
* and is just currently turned off.
|
||||
*/
|
||||
open class BluetoothNotEnabledException : BluetoothException("Bluetooth is not enabled")
|
||||
|
||||
/**
|
||||
* Exception thrown when trying to use Bluetooth even though there no adapter available.
|
||||
*
|
||||
* "Not available" typically means that the platform has no Bluetooth hardware, or that
|
||||
* said hardware is inaccessible.
|
||||
*/
|
||||
open class BluetoothNotAvailableException : BluetoothException("Bluetooth is not available - there is no usable adapter")
|
|
@ -140,6 +140,10 @@ interface BluetoothInterface {
|
|||
* a Bluetooth subsystem that has been shut down.
|
||||
* @throws BluetoothPermissionException if discovery fails because
|
||||
* scanning and connection permissions are missing.
|
||||
* @throws BluetoothNotEnabledException if the system's
|
||||
* Bluetooth adapter is currently not enabled.
|
||||
* @throws BluetoothNotAvailableException if the system's
|
||||
* Bluetooth adapter is currently not available.
|
||||
* @throws BluetoothException if discovery fails due to an underlying
|
||||
* Bluetooth issue.
|
||||
*/
|
||||
|
@ -172,6 +176,10 @@ interface BluetoothInterface {
|
|||
*
|
||||
* @return BluetoothDevice instance for the device with the
|
||||
* given address
|
||||
* @throws BluetoothNotEnabledException if the system's
|
||||
* Bluetooth adapter is currently not enabled.
|
||||
* @throws BluetoothNotAvailableException if the system's
|
||||
* Bluetooth adapter is currently not available.
|
||||
* @throws IllegalStateException if the interface is in a state
|
||||
* in which accessing devices is not possible, such as
|
||||
* a Bluetooth subsystem that has been shut down.
|
||||
|
@ -183,6 +191,8 @@ interface BluetoothInterface {
|
|||
*
|
||||
* @throws BluetoothPermissionException if getting the adapter name
|
||||
* fails because connection permissions are missing.
|
||||
* @throws BluetoothNotAvailableException if the system's
|
||||
* Bluetooth adapter is currently not available.
|
||||
* @throws BluetoothException if getting the adapter name fails
|
||||
* due to an underlying Bluetooth issue.
|
||||
*/
|
||||
|
@ -205,6 +215,11 @@ interface BluetoothInterface {
|
|||
* round, it is possible that between the [getPairedDeviceAddresses]
|
||||
* call and the [onDeviceUnpaired] assignment, a device is
|
||||
* unpaired, and thus does not get noticed.
|
||||
*
|
||||
* @throws BluetoothNotEnabledException if the system's
|
||||
* Bluetooth adapter is currently not enabled.
|
||||
* @throws BluetoothNotAvailableException if the system's
|
||||
* Bluetooth adapter is currently not available.
|
||||
*/
|
||||
fun getPairedDeviceAddresses(): Set<BluetoothAddress>
|
||||
}
|
||||
|
|
|
@ -249,6 +249,7 @@ class Pump(
|
|||
// Used for keeping track of wether an RT alert screen was already dismissed
|
||||
// (necessary since the screen may change its contents but still be the same screen).
|
||||
private var rtScreenAlreadyDismissed = false
|
||||
private var seenAlertAfterDismissingCounter = 0
|
||||
// Used in handleAlertScreenContent() to check if the current alert
|
||||
// screen contains the same alert as the previous one.
|
||||
private var lastObservedAlertScreenContent: AlertScreenContent? = null
|
||||
|
@ -2401,10 +2402,32 @@ class Pump(
|
|||
// the two button presses, so there is no need to wait
|
||||
// for the second screen - just press twice right away.
|
||||
if (!rtScreenAlreadyDismissed) {
|
||||
logger(LogLevel.DEBUG) { "Dismissing W$warningCode by short-pressing CHECK twice" }
|
||||
rtNavigationContext.shortPressButton(RTNavigationButton.CHECK)
|
||||
rtNavigationContext.shortPressButton(RTNavigationButton.CHECK)
|
||||
val numRequiredButtonPresses = when (alertScreenContent.state) {
|
||||
AlertScreenContent.AlertScreenState.TO_SNOOZE -> 2
|
||||
AlertScreenContent.AlertScreenState.TO_CONFIRM -> 1
|
||||
else -> throw AlertScreenException(alertScreenContent)
|
||||
}
|
||||
logger(LogLevel.DEBUG) { "Dismissing W$warningCode by short-pressing CHECK $numRequiredButtonPresses time(s)" }
|
||||
for (i in 1..numRequiredButtonPresses)
|
||||
rtNavigationContext.shortPressButton(RTNavigationButton.CHECK)
|
||||
rtScreenAlreadyDismissed = true
|
||||
} else {
|
||||
// In rare cases, an alert screen may still show after an alert
|
||||
// was dismissed. Unfortunately, it is not immediately clear if
|
||||
// this is the case, because the RT screen updates can come in
|
||||
// with some temporal jitter. So, to be safe, we only begin to
|
||||
// again handle alert screens after >10 alert screens were
|
||||
// observed in sequence.
|
||||
logger(LogLevel.DEBUG) { "W$warningCode already dismissed" }
|
||||
seenAlertAfterDismissingCounter++
|
||||
if (seenAlertAfterDismissingCounter > 10) {
|
||||
logger(LogLevel.WARN) {
|
||||
"Saw an alert screen $seenAlertAfterDismissingCounter time(s) " +
|
||||
"after having dismissed an alert twice; now again handling alerts"
|
||||
}
|
||||
rtScreenAlreadyDismissed = false
|
||||
seenAlertAfterDismissingCounter = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,8 +81,17 @@ sealed class MainScreenContent {
|
|||
* Possible contents of alert (= warning/error) screens.
|
||||
*/
|
||||
sealed class AlertScreenContent {
|
||||
data class Warning(val code: Int) : AlertScreenContent()
|
||||
data class Error(val code: Int) : AlertScreenContent()
|
||||
enum class AlertScreenState {
|
||||
TO_SNOOZE,
|
||||
TO_CONFIRM,
|
||||
// Used when the alert is an error. The text in error screens is not
|
||||
// interpreted, since it is anyway fully up to the user to interpret it.
|
||||
ERROR_TEXT,
|
||||
HISTORY_ENTRY
|
||||
}
|
||||
|
||||
data class Warning(val code: Int, val state: AlertScreenState) : AlertScreenContent()
|
||||
data class Error(val code: Int, val state: AlertScreenState) : AlertScreenContent()
|
||||
|
||||
/**
|
||||
* "Content" while the alert symbol & code currently are "blinked out".
|
||||
|
@ -1139,8 +1148,9 @@ class AlertScreenParser : Parser() {
|
|||
OptionalParser(SingleGlyphTypeParser(Glyph.LargeSymbol::class)), // warning/error symbol
|
||||
OptionalParser(SingleGlyphTypeParser(Glyph.LargeCharacter::class)), // "W" or "E"
|
||||
OptionalParser(IntegerParser()), // warning/error number
|
||||
OptionalParser(SingleGlyphTypeParser(Glyph.LargeSymbol::class)), // stop symbol (only with errors)
|
||||
SingleGlyphParser(Glyph.SmallSymbol(SmallSymbol.CHECK))
|
||||
OptionalParser(SingleGlyphTypeParser(Glyph.LargeSymbol::class)), // stop symbol (shown in suspended state)
|
||||
SingleGlyphParser(Glyph.SmallSymbol(SmallSymbol.CHECK)),
|
||||
StringParser() // snooze / confirm text
|
||||
)
|
||||
).parse(parseContext)
|
||||
|
||||
|
@ -1151,14 +1161,27 @@ class AlertScreenParser : Parser() {
|
|||
|
||||
return when (parseResult.valueAtOrNull<Glyph>(0)) {
|
||||
Glyph.LargeSymbol(LargeSymbol.WARNING) -> {
|
||||
val stateString = parseResult.valueAt<String>(4)
|
||||
val alertState = when (knownScreenTitles[stateString]) {
|
||||
TitleID.ALERT_TO_SNOOZE -> AlertScreenContent.AlertScreenState.TO_SNOOZE
|
||||
TitleID.ALERT_TO_CONFIRM -> AlertScreenContent.AlertScreenState.TO_CONFIRM
|
||||
else -> return ParseResult.Failed
|
||||
}
|
||||
ParseResult.Value(ParsedScreen.AlertScreen(
|
||||
AlertScreenContent.Warning(parseResult.valueAt(2))
|
||||
AlertScreenContent.Warning(parseResult.valueAt(2), alertState)
|
||||
))
|
||||
}
|
||||
|
||||
Glyph.LargeSymbol(LargeSymbol.ERROR) -> {
|
||||
ParseResult.Value(ParsedScreen.AlertScreen(
|
||||
AlertScreenContent.Error(parseResult.valueAt(2))
|
||||
AlertScreenContent.Error(
|
||||
parseResult.valueAt(2),
|
||||
// We don't really care about the state string if an error is shown.
|
||||
// It's not like any logic here will interpret it; that text is
|
||||
// purely for the user. So, don't bother interpreting it here, and
|
||||
// just assign a generic ERROR_TEXT state value instead.
|
||||
AlertScreenContent.AlertScreenState.ERROR_TEXT
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -1226,6 +1249,7 @@ class TemporaryBasalRatePercentageScreenParser : Parser() {
|
|||
override fun parseImpl(parseContext: ParseContext): ParseResult {
|
||||
val parseResult = SequenceParser(
|
||||
listOf(
|
||||
OptionalParser(SingleGlyphParser(Glyph.SmallSymbol(SmallSymbol.PERCENT))),
|
||||
SingleGlyphParser(Glyph.LargeSymbol(LargeSymbol.BASAL)),
|
||||
OptionalParser(IntegerParser()), // TBR percentage
|
||||
SingleGlyphParser(Glyph.LargeSymbol(LargeSymbol.PERCENT)),
|
||||
|
@ -1654,7 +1678,10 @@ class MyDataErrorDataScreenParser : Parser() {
|
|||
index = index,
|
||||
totalNumEntries = totalNumEntries,
|
||||
timestamp = timestamp,
|
||||
alert = if (alertType == SmallSymbol.WARNING) AlertScreenContent.Warning(alertNumber) else AlertScreenContent.Error(alertNumber)
|
||||
alert = if (alertType == SmallSymbol.WARNING)
|
||||
AlertScreenContent.Warning(alertNumber, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
else
|
||||
AlertScreenContent.Error(alertNumber, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1414,6 +1414,15 @@ val glyphPatterns = mapOf<Glyph, Pattern>(
|
|||
"█ ",
|
||||
"█████"
|
||||
)),
|
||||
Glyph.SmallCharacter('Ė') to Pattern(arrayOf(
|
||||
" █ ",
|
||||
" ",
|
||||
"█████",
|
||||
"█ ",
|
||||
"████ ",
|
||||
"█ ",
|
||||
"█████"
|
||||
)),
|
||||
Glyph.SmallCharacter('ę') to Pattern(arrayOf(
|
||||
"█████",
|
||||
"█ ",
|
||||
|
|
|
@ -19,7 +19,9 @@ enum class TitleID {
|
|||
BOLUS_DATA,
|
||||
ERROR_DATA,
|
||||
DAILY_TOTALS,
|
||||
TBR_DATA
|
||||
TBR_DATA,
|
||||
ALERT_TO_SNOOZE,
|
||||
ALERT_TO_CONFIRM
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,6 +48,8 @@ val knownScreenTitles = mapOf(
|
|||
"ERROR DATA" to TitleID.ERROR_DATA,
|
||||
"DAILY TOTALS" to TitleID.DAILY_TOTALS,
|
||||
"TBR DATA" to TitleID.TBR_DATA,
|
||||
"TO SNOOZE" to TitleID.ALERT_TO_SNOOZE,
|
||||
"TO CONFIRM" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Spanish
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -60,6 +64,8 @@ val knownScreenTitles = mapOf(
|
|||
"DATOS DE ERROR" to TitleID.ERROR_DATA,
|
||||
"TOTALES DIARIOS" to TitleID.DAILY_TOTALS,
|
||||
"DATOS DE DBT" to TitleID.TBR_DATA,
|
||||
"REPETIR SEÑAL" to TitleID.ALERT_TO_SNOOZE,
|
||||
"CONFIRMAR" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// French
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -74,6 +80,8 @@ val knownScreenTitles = mapOf(
|
|||
"ERREURS" to TitleID.ERROR_DATA,
|
||||
"QUANTITÉS JOURN." to TitleID.DAILY_TOTALS,
|
||||
"DBT" to TitleID.TBR_DATA,
|
||||
"RAPPEL TARD" to TitleID.ALERT_TO_SNOOZE, // actually, the text is "RAPPEL + TARD", but the + symbol is ignored to simplify parsing
|
||||
"POUR CONFIRMER" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Italian
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -88,6 +96,8 @@ val knownScreenTitles = mapOf(
|
|||
"MEMORIA ALLARMI" to TitleID.ERROR_DATA,
|
||||
"TOTALI GIORNATA" to TitleID.DAILY_TOTALS,
|
||||
"MEMORIA PBT" to TitleID.TBR_DATA,
|
||||
"RIPETI ALLARME" to TitleID.ALERT_TO_SNOOZE,
|
||||
"PER CONFERMARE" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Russian
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -102,6 +112,8 @@ val knownScreenTitles = mapOf(
|
|||
"ДАHHЫE OБ O ИБ." to TitleID.ERROR_DATA,
|
||||
"CУTOЧHЫE ДOЗЫ" to TitleID.DAILY_TOTALS,
|
||||
"ДАHHЫE O BБC" to TitleID.TBR_DATA,
|
||||
"BЫKЛ. ЗBУK" to TitleID.ALERT_TO_SNOOZE,
|
||||
"ПOДTBEPДИTЬ" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Turkish
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -116,6 +128,8 @@ val knownScreenTitles = mapOf(
|
|||
"HATA VERİLERİ" to TitleID.ERROR_DATA,
|
||||
"GÜNLÜK TOPLAM" to TitleID.DAILY_TOTALS,
|
||||
"GBH VERİLERİ" to TitleID.TBR_DATA,
|
||||
"ERTELE" to TitleID.ALERT_TO_SNOOZE,
|
||||
"ONAYLA" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Polish
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -130,6 +144,8 @@ val knownScreenTitles = mapOf(
|
|||
"DANE BŁĘDU" to TitleID.ERROR_DATA,
|
||||
"DZIEN. D. CAŁK." to TitleID.DAILY_TOTALS,
|
||||
"DANE TDP" to TitleID.TBR_DATA,
|
||||
"ABY WYCISZYĆ" to TitleID.ALERT_TO_SNOOZE,
|
||||
"ABY POTWIERDZ." to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Czech
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -144,6 +160,8 @@ val knownScreenTitles = mapOf(
|
|||
"ÚDAJE CHYB" to TitleID.ERROR_DATA,
|
||||
"CELK. DEN. DÁVKY" to TitleID.DAILY_TOTALS,
|
||||
"ÚDAJE DBD" to TitleID.TBR_DATA,
|
||||
"ODLOŽIT" to TitleID.ALERT_TO_SNOOZE,
|
||||
"POTVRDIT" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Hungarian
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -158,6 +176,8 @@ val knownScreenTitles = mapOf(
|
|||
"HIBAADATOK" to TitleID.ERROR_DATA,
|
||||
"NAPI TELJES" to TitleID.DAILY_TOTALS,
|
||||
"TBR-ADATOK" to TitleID.TBR_DATA,
|
||||
"NÉMÍTÁS" to TitleID.ALERT_TO_SNOOZE,
|
||||
"JÓVÁHAGYÁS" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Slovak
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -172,6 +192,8 @@ val knownScreenTitles = mapOf(
|
|||
"DÁTA O CHYBÁCH" to TitleID.ERROR_DATA,
|
||||
"SÚČTY DŇA" to TitleID.DAILY_TOTALS,
|
||||
"DBD DÁTA" to TitleID.TBR_DATA,
|
||||
"STLMI" to TitleID.ALERT_TO_SNOOZE,
|
||||
"POTVRDI" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Romanian
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -186,6 +208,8 @@ val knownScreenTitles = mapOf(
|
|||
"DATE EROARE" to TitleID.ERROR_DATA,
|
||||
"TOTALURI ZILNICE" to TitleID.DAILY_TOTALS,
|
||||
"DATE RBT" to TitleID.TBR_DATA,
|
||||
"OPRIRE SONERIE" to TitleID.ALERT_TO_SNOOZE,
|
||||
"CONFIRMARE" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Croatian
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -200,6 +224,8 @@ val knownScreenTitles = mapOf(
|
|||
"PODACI O GREŠK." to TitleID.ERROR_DATA,
|
||||
"UKUPNE DNEV.DOZE" to TitleID.DAILY_TOTALS,
|
||||
"PODACI O PBD-U" to TitleID.TBR_DATA,
|
||||
"ZA ODGODU" to TitleID.ALERT_TO_SNOOZE,
|
||||
"ZA POTVRDU" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Dutch
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -214,6 +240,8 @@ val knownScreenTitles = mapOf(
|
|||
"FOUTENGEGEVENS" to TitleID.ERROR_DATA,
|
||||
"DAGTOTALEN" to TitleID.DAILY_TOTALS,
|
||||
"TBD-GEGEVENS" to TitleID.TBR_DATA,
|
||||
"UITSTELLEN" to TitleID.ALERT_TO_SNOOZE,
|
||||
"BEVESTIGEN" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Greek
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -228,6 +256,8 @@ val knownScreenTitles = mapOf(
|
|||
"ΔEΔOМ. ΣΦАΛМАTΩN" to TitleID.ERROR_DATA,
|
||||
"HМEPHΣIO ΣΥNOΛO" to TitleID.DAILY_TOTALS,
|
||||
"ΔEΔOМENА П.B.P." to TitleID.TBR_DATA,
|
||||
"ANAΣTOΛH" to TitleID.ALERT_TO_SNOOZE,
|
||||
"EПIBEBАIΩΣH" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Finnish
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -242,6 +272,8 @@ val knownScreenTitles = mapOf(
|
|||
"HÄLYTYSTIEDOT" to TitleID.ERROR_DATA,
|
||||
"PÄIV. KOK.ANNOS" to TitleID.DAILY_TOTALS,
|
||||
"TBA - TIEDOT" to TitleID.TBR_DATA,
|
||||
"ILMOITA MYÖH." to TitleID.ALERT_TO_SNOOZE,
|
||||
"VAHVISTA" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Norwegian
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -256,6 +288,8 @@ val knownScreenTitles = mapOf(
|
|||
"FEILDATA" to TitleID.ERROR_DATA,
|
||||
"DØGNMENGDE" to TitleID.DAILY_TOTALS,
|
||||
"MBD-DATA" to TitleID.TBR_DATA,
|
||||
"FOR Å SLUMRE" to TitleID.ALERT_TO_SNOOZE,
|
||||
"FOR Å BEKREFTE" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Portuguese
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -271,6 +305,8 @@ val knownScreenTitles = mapOf(
|
|||
"DADOS DE ERROS" to TitleID.ERROR_DATA, "DADOS DE ALARMES" to TitleID.ERROR_DATA,
|
||||
"TOTAIS DIÁRIOS" to TitleID.DAILY_TOTALS,
|
||||
"DADOS DBT" to TitleID.TBR_DATA,
|
||||
"PARA SILENCIAR" to TitleID.ALERT_TO_SNOOZE,
|
||||
"PARA CONFIRMAR" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Swedish
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -285,6 +321,8 @@ val knownScreenTitles = mapOf(
|
|||
"FELDATA" to TitleID.ERROR_DATA,
|
||||
"DYGNSHISTORIK" to TitleID.DAILY_TOTALS,
|
||||
"TBD DATA" to TitleID.TBR_DATA,
|
||||
"SNOOZE" to TitleID.ALERT_TO_SNOOZE,
|
||||
"BEKRÄFTA" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Danish
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -299,6 +337,8 @@ val knownScreenTitles = mapOf(
|
|||
"FEJLDATA" to TitleID.ERROR_DATA,
|
||||
"DAGLIG TOTAL" to TitleID.DAILY_TOTALS,
|
||||
"MBR-DATA" to TitleID.TBR_DATA,
|
||||
"FOR AT UDSÆTTE" to TitleID.ALERT_TO_SNOOZE,
|
||||
"FOR GODKEND" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// German
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
|
@ -313,6 +353,40 @@ val knownScreenTitles = mapOf(
|
|||
"FEHLERMELDUNGEN" to TitleID.ERROR_DATA,
|
||||
"TAGESGESAMTMENGE" to TitleID.DAILY_TOTALS,
|
||||
"TBR-INFORMATION" to TitleID.TBR_DATA,
|
||||
"NEU ERINNERN" to TitleID.ALERT_TO_SNOOZE,
|
||||
"BESTÄTIGEN" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Slovenian
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
"ODSTOTEK ZBO" to TitleID.TBR_PERCENTAGE,
|
||||
"TRAJANJE ZBO" to TitleID.TBR_DURATION,
|
||||
"URA" to TitleID.HOUR,
|
||||
"MINUTE" to TitleID.MINUTE,
|
||||
"LETO" to TitleID.YEAR,
|
||||
"MESEC" to TitleID.MONTH,
|
||||
"DAN" to TitleID.DAY,
|
||||
"PODATKI O BOLUSU" to TitleID.BOLUS_DATA,
|
||||
"PODATKI O NAPAKI" to TitleID.ERROR_DATA,
|
||||
"DNEVNA PORABA" to TitleID.DAILY_TOTALS,
|
||||
"PODATKI O ZBO" to TitleID.TBR_DATA,
|
||||
"UTIŠANJE" to TitleID.ALERT_TO_SNOOZE,
|
||||
"POTRDITEV" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Lithuanian
|
||||
"QUICK INFO" to TitleID.QUICK_INFO,
|
||||
"TBR REIKŠMĖS" to TitleID.TBR_PERCENTAGE,
|
||||
"TBR TRUKMĖ" to TitleID.TBR_DURATION,
|
||||
"VALANDA" to TitleID.HOUR,
|
||||
"MINUTĖ" to TitleID.MINUTE,
|
||||
"METAI" to TitleID.YEAR,
|
||||
"MĖNUO" to TitleID.MONTH,
|
||||
"DIENA" to TitleID.DAY,
|
||||
"BOLIUSO DUOMENYS" to TitleID.BOLUS_DATA,
|
||||
"KLAIDOS DUOMENYS" to TitleID.ERROR_DATA,
|
||||
"BENDR. DIENOS K." to TitleID.DAILY_TOTALS,
|
||||
"TBR DUOMENYS" to TitleID.TBR_DATA,
|
||||
"NUTILDYTI" to TitleID.ALERT_TO_SNOOZE,
|
||||
"PATVIRTINTI" to TitleID.ALERT_TO_CONFIRM,
|
||||
|
||||
// Some pumps came preconfigured with a different quick info name
|
||||
"ACCU CHECK SPIRIT" to TitleID.QUICK_INFO
|
||||
|
|
|
@ -13,7 +13,7 @@ import info.nightscout.comboctl.parser.ParsedScreen
|
|||
import info.nightscout.comboctl.parser.testFrameMainScreenWithTimeSeparator
|
||||
import info.nightscout.comboctl.parser.testFrameMainScreenWithoutTimeSeparator
|
||||
import info.nightscout.comboctl.parser.testFrameStandardBolusMenuScreen
|
||||
import info.nightscout.comboctl.parser.testFrameTbrDurationEnglishScreen
|
||||
import info.nightscout.comboctl.parser.TbrPercentageAndDurationScreens
|
||||
import info.nightscout.comboctl.parser.testFrameTemporaryBasalRateNoPercentageScreen
|
||||
import info.nightscout.comboctl.parser.testFrameTemporaryBasalRatePercentage110Screen
|
||||
import info.nightscout.comboctl.parser.testFrameW6CancelTbrWarningScreen
|
||||
|
@ -300,7 +300,7 @@ class ParsedDisplayFrameStreamTest {
|
|||
// We expect normal parsing behavior.
|
||||
stream.feedDisplayFrame(testFrameW6CancelTbrWarningScreen)
|
||||
val parsedWarningFrame = stream.getParsedDisplayFrame(processAlertScreens = false)
|
||||
assertEquals(ParsedScreen.AlertScreen(AlertScreenContent.Warning(6)), parsedWarningFrame!!.parsedScreen)
|
||||
assertEquals(ParsedScreen.AlertScreen(AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)), parsedWarningFrame!!.parsedScreen)
|
||||
|
||||
// Feed a W6 screen, but with alert screen detection enabled.
|
||||
// We expect the alert screen to be detected and an exception
|
||||
|
@ -328,7 +328,7 @@ class ParsedDisplayFrameStreamTest {
|
|||
val displayFrameList = listOf(
|
||||
testFrameTemporaryBasalRatePercentage110Screen,
|
||||
testFrameTemporaryBasalRateNoPercentageScreen,
|
||||
testFrameTbrDurationEnglishScreen
|
||||
TbrPercentageAndDurationScreens.testFrameTbrDurationEnglishScreen
|
||||
)
|
||||
|
||||
val parsedFrameList = mutableListOf<ParsedDisplayFrame>()
|
||||
|
|
|
@ -723,7 +723,7 @@ class RTNavigationTest {
|
|||
)),
|
||||
ParsedScreen.BasalRate1ProgrammingMenuScreen,
|
||||
ParsedScreen.BasalRate2ProgrammingMenuScreen,
|
||||
ParsedScreen.AlertScreen(AlertScreenContent.Warning(code = 6)),
|
||||
ParsedScreen.AlertScreen(AlertScreenContent.Warning(code = 6, AlertScreenContent.AlertScreenState.TO_SNOOZE)),
|
||||
ParsedScreen.BasalRate3ProgrammingMenuScreen,
|
||||
ParsedScreen.BasalRate4ProgrammingMenuScreen,
|
||||
ParsedScreen.BasalRate5ProgrammingMenuScreen
|
||||
|
@ -864,7 +864,7 @@ class RTNavigationTest {
|
|||
ParsedScreen.TemporaryBasalRatePercentageScreen(170, remainingDurationInMinutes = 30),
|
||||
ParsedScreen.TemporaryBasalRatePercentageScreen(170, remainingDurationInMinutes = 30),
|
||||
ParsedScreen.TemporaryBasalRatePercentageScreen(170, remainingDurationInMinutes = 30),
|
||||
ParsedScreen.AlertScreen(AlertScreenContent.Warning(code = 6)),
|
||||
ParsedScreen.AlertScreen(AlertScreenContent.Warning(code = 6, AlertScreenContent.AlertScreenState.TO_SNOOZE)),
|
||||
ParsedScreen.TemporaryBasalRatePercentageScreen(160, remainingDurationInMinutes = 30),
|
||||
ParsedScreen.TemporaryBasalRatePercentageScreen(160, remainingDurationInMinutes = 30)
|
||||
))
|
||||
|
|
|
@ -679,16 +679,16 @@ class ParserTest {
|
|||
|
||||
assertEquals(ParseResult.Value::class, result::class)
|
||||
val alertScreen = (result as ParseResult.Value<*>).value as ParsedScreen.AlertScreen
|
||||
assertEquals(AlertScreenContent.Warning(6), alertScreen.content)
|
||||
assertEquals(AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE), alertScreen.content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkW8CancelBolusWarningScreenParsing() {
|
||||
val testScreens = listOf(
|
||||
Pair(testFrameW8CancelBolusWarningScreen0, AlertScreenContent.None),
|
||||
Pair(testFrameW8CancelBolusWarningScreen1, AlertScreenContent.Warning(8)),
|
||||
Pair(testFrameW8CancelBolusWarningScreen1, AlertScreenContent.Warning(8, AlertScreenContent.AlertScreenState.TO_SNOOZE)),
|
||||
Pair(testFrameW8CancelBolusWarningScreen2, AlertScreenContent.None),
|
||||
Pair(testFrameW8CancelBolusWarningScreen3, AlertScreenContent.Warning(8))
|
||||
Pair(testFrameW8CancelBolusWarningScreen3, AlertScreenContent.Warning(8, AlertScreenContent.AlertScreenState.TO_CONFIRM))
|
||||
)
|
||||
|
||||
for (testScreen in testScreens) {
|
||||
|
@ -704,7 +704,25 @@ class ParserTest {
|
|||
fun checkE2BatteryEmptyErrorScreenParsing() {
|
||||
val testScreens = listOf(
|
||||
Pair(testFrameE2BatteryEmptyErrorScreen0, AlertScreenContent.None),
|
||||
Pair(testFrameE2BatteryEmptyErrorScreen1, AlertScreenContent.Error(2))
|
||||
Pair(testFrameE2BatteryEmptyErrorScreen1, AlertScreenContent.Error(2, AlertScreenContent.AlertScreenState.ERROR_TEXT))
|
||||
)
|
||||
|
||||
for (testScreen in testScreens) {
|
||||
val testContext = TestContext(testScreen.first, 0, skipTitleString = true)
|
||||
val result = AlertScreenParser().parse(testContext.parseContext)
|
||||
assertEquals(ParseResult.Value::class, result::class)
|
||||
val screen = (result as ParseResult.Value<*>).value as ParsedScreen.AlertScreen
|
||||
assertEquals(testScreen.second, screen.content)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkE4OcclusionErrorScreenParsing() {
|
||||
val testScreens = listOf(
|
||||
Pair(testFrameE4OcclusionErrorScreen0, AlertScreenContent.Error(4, AlertScreenContent.AlertScreenState.ERROR_TEXT)),
|
||||
Pair(testFrameE4OcclusionErrorScreen1, AlertScreenContent.None),
|
||||
Pair(testFrameE4OcclusionErrorScreen2, AlertScreenContent.Error(4, AlertScreenContent.AlertScreenState.ERROR_TEXT)),
|
||||
Pair(testFrameE4OcclusionErrorScreen3, AlertScreenContent.None),
|
||||
)
|
||||
|
||||
for (testScreen in testScreens) {
|
||||
|
@ -722,26 +740,28 @@ class ParserTest {
|
|||
Pair(testFrameTemporaryBasalRatePercentage100Screen, 100),
|
||||
Pair(testFrameTemporaryBasalRatePercentage110Screen, 110),
|
||||
Pair(testFrameTemporaryBasalRateNoPercentageScreen, null),
|
||||
Pair(testFrameTbrPercentageEnglishScreen, 110),
|
||||
Pair(testFrameTbrPercentageSpanishScreen, 110),
|
||||
Pair(testFrameTbrPercentageFrenchScreen, 110),
|
||||
Pair(testFrameTbrPercentageItalianScreen, 110),
|
||||
Pair(testFrameTbrPercentageRussianScreen, 110),
|
||||
Pair(testFrameTbrPercentageTurkishScreen, 110),
|
||||
Pair(testFrameTbrPercentagePolishScreen, 100),
|
||||
Pair(testFrameTbrPercentageCzechScreen, 110),
|
||||
Pair(testFrameTbrPercentageHungarianScreen, 110),
|
||||
Pair(testFrameTbrPercentageSlovakScreen, 110),
|
||||
Pair(testFrameTbrPercentageRomanianScreen, 110),
|
||||
Pair(testFrameTbrPercentageCroatianScreen, 110),
|
||||
Pair(testFrameTbrPercentageDutchScreen, 110),
|
||||
Pair(testFrameTbrPercentageGreekScreen, 110),
|
||||
Pair(testFrameTbrPercentageFinnishScreen, 110),
|
||||
Pair(testFrameTbrPercentageNorwegianScreen, 110),
|
||||
Pair(testFrameTbrPercentagePortugueseScreen, 110),
|
||||
Pair(testFrameTbrPercentageSwedishScreen, 110),
|
||||
Pair(testFrameTbrPercentageDanishScreen, 110),
|
||||
Pair(testFrameTbrPercentageGermanScreen, 110)
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageEnglishScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageSpanishScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageFrenchScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageItalianScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageRussianScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageTurkishScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentagePolishScreen, 100),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageCzechScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageHungarianScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageSlovakScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageRomanianScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageCroatianScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageDutchScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageGreekScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageFinnishScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageNorwegianScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentagePortugueseScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageSwedishScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageDanishScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageGermanScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageSlovenianScreen, 110),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrPercentageLithuanianScreen, 110),
|
||||
)
|
||||
|
||||
for (testScreen in testScreens) {
|
||||
|
@ -769,26 +789,28 @@ class ParserTest {
|
|||
fun checkTemporaryBasalRateDurationScreenParsing() {
|
||||
val testScreens = listOf(
|
||||
Pair(testFrameTbrDurationNoDurationScreen, null),
|
||||
Pair(testFrameTbrDurationEnglishScreen, 30),
|
||||
Pair(testFrameTbrDurationSpanishScreen, 30),
|
||||
Pair(testFrameTbrDurationFrenchScreen, 30),
|
||||
Pair(testFrameTbrDurationItalianScreen, 30),
|
||||
Pair(testFrameTbrDurationRussianScreen, 30),
|
||||
Pair(testFrameTbrDurationTurkishScreen, 30),
|
||||
Pair(testFrameTbrDurationPolishScreen, 30),
|
||||
Pair(testFrameTbrDurationCzechScreen, 30),
|
||||
Pair(testFrameTbrDurationHungarianScreen, 30),
|
||||
Pair(testFrameTbrDurationSlovakScreen, 30),
|
||||
Pair(testFrameTbrDurationRomanianScreen, 30),
|
||||
Pair(testFrameTbrDurationCroatianScreen, 30),
|
||||
Pair(testFrameTbrDurationDutchScreen, 30),
|
||||
Pair(testFrameTbrDurationGreekScreen, 30),
|
||||
Pair(testFrameTbrDurationFinnishScreen, 30),
|
||||
Pair(testFrameTbrDurationNorwegianScreen, 30),
|
||||
Pair(testFrameTbrDurationPortugueseScreen, 30),
|
||||
Pair(testFrameTbrDurationSwedishScreen, 30),
|
||||
Pair(testFrameTbrDurationDanishScreen, 30),
|
||||
Pair(testFrameTbrDurationGermanScreen, 30)
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationEnglishScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationSpanishScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationFrenchScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationItalianScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationRussianScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationTurkishScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationPolishScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationCzechScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationHungarianScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationSlovakScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationRomanianScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationCroatianScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationDutchScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationGreekScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationFinnishScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationNorwegianScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationPortugueseScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationSwedishScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationDanishScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationGermanScreen, 30),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationSlovenianScreen, 15),
|
||||
Pair(TbrPercentageAndDurationScreens.testFrameTbrDurationLithuanianScreen, 15),
|
||||
)
|
||||
|
||||
for (testScreen in testScreens) {
|
||||
|
@ -927,6 +949,18 @@ class ParserTest {
|
|||
Pair(testTimeAndDateSettingsMonthGermanScreen, ParsedScreen.TimeAndDateSettingsMonthScreen(4)),
|
||||
Pair(testTimeAndDateSettingsDayGermanScreen, ParsedScreen.TimeAndDateSettingsDayScreen(21)),
|
||||
|
||||
Pair(testTimeAndDateSettingsHourSlovenianScreen, ParsedScreen.TimeAndDateSettingsHourScreen(19)),
|
||||
Pair(testTimeAndDateSettingsMinuteSlovenianScreen, ParsedScreen.TimeAndDateSettingsMinuteScreen(50)),
|
||||
Pair(testTimeAndDateSettingsYearSlovenianScreen, ParsedScreen.TimeAndDateSettingsYearScreen(2023)),
|
||||
Pair(testTimeAndDateSettingsMonthSlovenianScreen, ParsedScreen.TimeAndDateSettingsMonthScreen(3)),
|
||||
Pair(testTimeAndDateSettingsDaySlovenianScreen, ParsedScreen.TimeAndDateSettingsDayScreen(8)),
|
||||
|
||||
Pair(testTimeAndDateSettingsHourLithuanianScreen, ParsedScreen.TimeAndDateSettingsHourScreen(20)),
|
||||
Pair(testTimeAndDateSettingsMinuteLithuanianScreen, ParsedScreen.TimeAndDateSettingsMinuteScreen(16)),
|
||||
Pair(testTimeAndDateSettingsYearLithuanianScreen, ParsedScreen.TimeAndDateSettingsYearScreen(2023)),
|
||||
Pair(testTimeAndDateSettingsMonthLithuanianScreen, ParsedScreen.TimeAndDateSettingsMonthScreen(3)),
|
||||
Pair(testTimeAndDateSettingsDayLithuanianScreen, ParsedScreen.TimeAndDateSettingsDayScreen(8)),
|
||||
|
||||
// Extra test to verify that a *minute* 24 is not incorrectly interpreted
|
||||
// as an *hour* 24 and thus translated to 0 (this is done because the Combo
|
||||
// may show midnight as both hour 0 and hour 24).
|
||||
|
@ -963,7 +997,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 1, dayOfMonth = 28, hour = 11, minute = 0, second = 0),
|
||||
alert = AlertScreenContent.Warning(6)
|
||||
alert = AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -994,7 +1028,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 1, dayOfMonth = 28, hour = 11, minute = 0, second = 0),
|
||||
alert = AlertScreenContent.Warning(6)
|
||||
alert = AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1025,7 +1059,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0),
|
||||
alert = AlertScreenContent.Warning(7)
|
||||
alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1056,7 +1090,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0),
|
||||
alert = AlertScreenContent.Warning(7)
|
||||
alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1087,7 +1121,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0),
|
||||
alert = AlertScreenContent.Warning(7)
|
||||
alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1118,7 +1152,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0),
|
||||
alert = AlertScreenContent.Warning(7)
|
||||
alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1149,7 +1183,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0),
|
||||
alert = AlertScreenContent.Warning(7)
|
||||
alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1180,7 +1214,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0),
|
||||
alert = AlertScreenContent.Warning(7)
|
||||
alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1211,7 +1245,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0),
|
||||
alert = AlertScreenContent.Warning(7)
|
||||
alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1242,7 +1276,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0),
|
||||
alert = AlertScreenContent.Warning(7)
|
||||
alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1273,7 +1307,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 5, dayOfMonth = 11, hour = 21, minute = 56, second = 0),
|
||||
alert = AlertScreenContent.Warning(7)
|
||||
alert = AlertScreenContent.Warning(7, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1304,7 +1338,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 2, dayOfMonth = 1, hour = 1, minute = 6, second = 0),
|
||||
alert = AlertScreenContent.Warning(1)
|
||||
alert = AlertScreenContent.Warning(1, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1335,7 +1369,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 2, dayOfMonth = 1, hour = 1, minute = 6, second = 0),
|
||||
alert = AlertScreenContent.Warning(1)
|
||||
alert = AlertScreenContent.Warning(1, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1366,7 +1400,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 2, dayOfMonth = 1, hour = 1, minute = 6, second = 0),
|
||||
alert = AlertScreenContent.Warning(1)
|
||||
alert = AlertScreenContent.Warning(1, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1397,7 +1431,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 2, dayOfMonth = 1, hour = 1, minute = 6, second = 0),
|
||||
alert = AlertScreenContent.Warning(1)
|
||||
alert = AlertScreenContent.Warning(1, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1428,7 +1462,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 2, dayOfMonth = 1, hour = 1, minute = 6, second = 0),
|
||||
alert = AlertScreenContent.Warning(1)
|
||||
alert = AlertScreenContent.Warning(1, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1459,7 +1493,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 2, dayOfMonth = 1, hour = 1, minute = 6, second = 0),
|
||||
alert = AlertScreenContent.Warning(1)
|
||||
alert = AlertScreenContent.Warning(1, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1490,7 +1524,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 2, dayOfMonth = 1, hour = 1, minute = 6, second = 0),
|
||||
alert = AlertScreenContent.Warning(1)
|
||||
alert = AlertScreenContent.Warning(1, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1521,7 +1555,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 2, dayOfMonth = 1, hour = 1, minute = 6, second = 0),
|
||||
alert = AlertScreenContent.Warning(1)
|
||||
alert = AlertScreenContent.Warning(1, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1552,7 +1586,7 @@ class ParserTest {
|
|||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 1, dayOfMonth = 28, hour = 11, minute = 0, second = 0),
|
||||
alert = AlertScreenContent.Warning(6)
|
||||
alert = AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
|
@ -1569,7 +1603,69 @@ class ParserTest {
|
|||
timestamp = LocalDateTime(year = 0, monthNumber = 1, dayOfMonth = 28, hour = 11, minute = 0, second = 0),
|
||||
percentage = 110, durationInMinutes = 0
|
||||
)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
testMyDataBolusDataSlovenianScreen,
|
||||
ParsedScreen.MyDataBolusDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 1, dayOfMonth = 1, hour = 23, minute = 21, second = 0),
|
||||
bolusAmount = 3200, bolusType = MyDataBolusType.MULTI_WAVE, durationInMinutes = 43
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
testMyDataErrorDataSlovenianScreen,
|
||||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 3, dayOfMonth = 8, hour = 17, minute = 31, second = 0),
|
||||
alert = AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
testMyDataDailyTotalsSlovenianScreen,
|
||||
ParsedScreen.MyDataDailyTotalsScreen(
|
||||
index = 1, totalNumEntries = 30, date = LocalDate(year = 0, dayOfMonth = 8, monthNumber = 3),
|
||||
totalDailyAmount = 32100
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
testMyDataTbrDataSlovenianScreen,
|
||||
ParsedScreen.MyDataTbrDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 3, dayOfMonth = 8, hour = 17, minute = 31, second = 0),
|
||||
percentage = 110, durationInMinutes = 1
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
testMyDataBolusDataLithuanianScreen,
|
||||
ParsedScreen.MyDataBolusDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 1, dayOfMonth = 1, hour = 23, minute = 21, second = 0),
|
||||
bolusAmount = 3200, bolusType = MyDataBolusType.MULTI_WAVE, durationInMinutes = 43
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
testMyDataErrorDataLithuanianScreen,
|
||||
ParsedScreen.MyDataErrorDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 3, dayOfMonth = 8, hour = 20, minute = 6, second = 0),
|
||||
alert = AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.HISTORY_ENTRY)
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
testMyDataDailyTotalsLithuanianScreen,
|
||||
ParsedScreen.MyDataDailyTotalsScreen(
|
||||
index = 1, totalNumEntries = 30, date = LocalDate(year = 0, dayOfMonth = 8, monthNumber = 3),
|
||||
totalDailyAmount = 33600
|
||||
)
|
||||
),
|
||||
Pair(
|
||||
testMyDataTbrDataLithuanianScreen,
|
||||
ParsedScreen.MyDataTbrDataScreen(
|
||||
index = 1, totalNumEntries = 30,
|
||||
timestamp = LocalDateTime(year = 0, monthNumber = 3, dayOfMonth = 8, hour = 20, minute = 6, second = 0),
|
||||
percentage = 110, durationInMinutes = 1
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
for (testScreen in testScreens) {
|
||||
|
@ -1595,6 +1691,196 @@ class ParserTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkAlertScreenTextParsing() {
|
||||
val testScreens = listOf(
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextEnglishScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextEnglishScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextSpanishScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextSpanishScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextFrenchScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextFrenchScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextItalianScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextItalianScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextRussianScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextRussianScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextTurkishScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextTurkishScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextPolishScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextPolishScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextCzechScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextCzechScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextHungarianScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextHungarianScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextSlovakScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextSlovakScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextRomanianScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextRomanianScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextCroatianScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextCroatianScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextDutchScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextDutchScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextGreekScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextGreekScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextFinnishScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextFinnishScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextNorwegianScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextNorwegianScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextPortugueseScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextPortugueseScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextSwedishScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextSwedishScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextDanishScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextDanishScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextGermanScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextGermanScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextSlovenianScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextSlovenianScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenSnoozeTextLithuanianScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_SNOOZE)
|
||||
),
|
||||
Pair(
|
||||
AlertSnoozeAndConfirmScreens.testAlertScreenConfirmTextLithuanianScreen,
|
||||
AlertScreenContent.Warning(6, AlertScreenContent.AlertScreenState.TO_CONFIRM)
|
||||
),
|
||||
)
|
||||
|
||||
for (testScreen in testScreens) {
|
||||
val testContext = TestContext(testScreen.first, 0, skipTitleString = true)
|
||||
val result = AlertScreenParser().parse(testContext.parseContext)
|
||||
|
||||
assertEquals(ParseResult.Value::class, result::class)
|
||||
val alertScreen = (result as ParseResult.Value<*>).value as ParsedScreen.AlertScreen
|
||||
assertEquals(testScreen.second, alertScreen.content)
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun checkToplevelScreenParsing() {
|
||||
val testScreens = listOf(
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -5,6 +5,7 @@ import info.nightscout.comboctl.base.CurrentTbrState
|
|||
import info.nightscout.comboctl.base.InvariantPumpData
|
||||
import info.nightscout.comboctl.base.Nonce
|
||||
import info.nightscout.comboctl.base.PumpStateStore
|
||||
import info.nightscout.comboctl.base.PumpStateStoreAccessException
|
||||
import info.nightscout.comboctl.base.Tbr
|
||||
import info.nightscout.comboctl.base.toBluetoothAddress
|
||||
import info.nightscout.comboctl.base.toCipher
|
||||
|
@ -151,7 +152,7 @@ class AAPSPumpStateStore(
|
|||
timestamp = Instant.fromEpochSeconds(tbrTimestamp),
|
||||
percentage = tbrPercentage,
|
||||
durationInMinutes = tbrDuration,
|
||||
type = Tbr.Type.fromStringId(tbrType)!!
|
||||
type = Tbr.Type.fromStringId(tbrType) ?: throw PumpStateStoreAccessException(pumpAddress, "Invalid type \"$tbrType\"")
|
||||
))
|
||||
else
|
||||
CurrentTbrState.NoTbrOngoing
|
||||
|
|
|
@ -94,8 +94,13 @@ class ComboV2Fragment : DaggerFragment() {
|
|||
binding.combov2DriverState.text = text
|
||||
|
||||
binding.combov2RefreshButton.isEnabled = when (connectionState) {
|
||||
// Enable the refresh button if:
|
||||
// 1. Pump is not connected (to be able to manually initiate a pump status update)
|
||||
// 2. Pump is suspended (in case the user resumed the pump and wants to update the status in AAPS)
|
||||
// 3. When an error happened (to manually clear the pumpErrorObserved flag and unlock the loop after dealing with the error)
|
||||
ComboV2Plugin.DriverState.Disconnected,
|
||||
ComboV2Plugin.DriverState.Suspended -> true
|
||||
ComboV2Plugin.DriverState.Suspended,
|
||||
ComboV2Plugin.DriverState.Error-> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import dagger.android.HasAndroidInjector
|
|||
import info.nightscout.comboctl.android.AndroidBluetoothInterface
|
||||
import info.nightscout.comboctl.base.BasicProgressStage
|
||||
import info.nightscout.comboctl.base.BluetoothException
|
||||
import info.nightscout.comboctl.base.BluetoothNotEnabledException
|
||||
import info.nightscout.comboctl.base.ComboException
|
||||
import info.nightscout.comboctl.base.DisplayFrame
|
||||
import info.nightscout.comboctl.base.NullDisplayFrame
|
||||
|
@ -65,7 +66,6 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
|
@ -134,8 +134,8 @@ class ComboV2Plugin @Inject constructor (
|
|||
|
||||
// Coroutine scope and the associated job. All coroutines
|
||||
// that are started in this plugin are part of this scope.
|
||||
private val pumpCoroutineMainJob = SupervisorJob()
|
||||
private val pumpCoroutineScope = CoroutineScope(Dispatchers.Default + pumpCoroutineMainJob)
|
||||
private var pumpCoroutineScopeJob = SupervisorJob()
|
||||
private var pumpCoroutineScope = CoroutineScope(Dispatchers.Default + pumpCoroutineScopeJob)
|
||||
|
||||
private val _pumpDescription = PumpDescription()
|
||||
|
||||
|
@ -255,6 +255,8 @@ class ComboV2Plugin @Inject constructor (
|
|||
}
|
||||
|
||||
override fun onStart() {
|
||||
aapsLogger.info(LTag.PUMP, "Starting combov2 driver")
|
||||
|
||||
super.onStart()
|
||||
|
||||
updateComboCtlLogLevel()
|
||||
|
@ -288,51 +290,86 @@ class ComboV2Plugin @Inject constructor (
|
|||
}
|
||||
|
||||
aapsLogger.debug(LTag.PUMP, "Creating bluetooth interface")
|
||||
bluetoothInterface = AndroidBluetoothInterface(context)
|
||||
val newBluetoothInterface = AndroidBluetoothInterface(context)
|
||||
bluetoothInterface = newBluetoothInterface
|
||||
|
||||
aapsLogger.info(LTag.PUMP, "Continuing combov2 driver start in coroutine")
|
||||
|
||||
// Continue initialization in a separate coroutine. This allows us to call
|
||||
// runWithPermissionCheck(), which will keep trying to run the code block
|
||||
// until either the necessary Bluetooth permissions are granted, or the
|
||||
// coroutine is cancelled (see onStop() below).
|
||||
pumpCoroutineScope.launch {
|
||||
runWithPermissionCheck(
|
||||
context, config, aapsLogger, androidPermission,
|
||||
permissionsToCheckFor = listOf("android.permission.BLUETOOTH_CONNECT")
|
||||
) {
|
||||
aapsLogger.debug(LTag.PUMP, "Setting up bluetooth interface")
|
||||
bluetoothInterface!!.setup()
|
||||
try {
|
||||
runWithPermissionCheck(
|
||||
context, config, aapsLogger, androidPermission,
|
||||
permissionsToCheckFor = listOf("android.permission.BLUETOOTH_CONNECT")
|
||||
) {
|
||||
aapsLogger.debug(LTag.PUMP, "Setting up bluetooth interface")
|
||||
|
||||
aapsLogger.debug(LTag.PUMP, "Setting up pump manager")
|
||||
pumpManager = ComboCtlPumpManager(bluetoothInterface!!, pumpStateStore)
|
||||
pumpManager!!.setup {
|
||||
_pairedStateUIFlow.value = false
|
||||
unpairing = false
|
||||
try {
|
||||
newBluetoothInterface.setup()
|
||||
|
||||
rxBus.send(EventDismissNotification(Notification.BLUETOOTH_NOT_ENABLED))
|
||||
|
||||
aapsLogger.debug(LTag.PUMP, "Setting up pump manager")
|
||||
val newPumpManager = ComboCtlPumpManager(newBluetoothInterface, pumpStateStore)
|
||||
newPumpManager.setup {
|
||||
_pairedStateUIFlow.value = false
|
||||
unpairing = false
|
||||
}
|
||||
|
||||
// UI flows that must have defined values right
|
||||
// at start are initialized here.
|
||||
|
||||
// The paired state UI flow is special in that it is also
|
||||
// used as the backing store for the isPaired() function,
|
||||
// so setting up that UI state flow equals updating that
|
||||
// paired state.
|
||||
val paired = newPumpManager.getPairedPumpAddresses().isNotEmpty()
|
||||
_pairedStateUIFlow.value = paired
|
||||
|
||||
pumpManager = newPumpManager
|
||||
} catch (_: BluetoothNotEnabledException) {
|
||||
uiInteraction.addNotification(
|
||||
Notification.BLUETOOTH_NOT_ENABLED,
|
||||
text = rh.gs(info.nightscout.core.ui.R.string.ble_not_enabled),
|
||||
level = Notification.INFO
|
||||
)
|
||||
|
||||
// If the user currently has Bluetooth disabled, retry until
|
||||
// the user turns it on. AAPS will automatically show a dialog
|
||||
// box which requests the user to enable Bluetooth. Upon
|
||||
// catching this exception, runWithPermissionCheck() will wait
|
||||
// a bit before retrying, so no delay() call is needed here.
|
||||
throw RetryPermissionCheckException()
|
||||
}
|
||||
|
||||
setDriverState(DriverState.Disconnected)
|
||||
|
||||
aapsLogger.info(LTag.PUMP, "combov2 driver start complete")
|
||||
|
||||
// NOTE: EventInitializationChanged is sent in getPumpStatus() .
|
||||
}
|
||||
|
||||
// UI flows that must have defined values right
|
||||
// at start are initialized here.
|
||||
|
||||
// The paired state UI flow is special in that it is also
|
||||
// used as the backing store for the isPaired() function,
|
||||
// so setting up that UI state flow equals updating that
|
||||
// paired state.
|
||||
val paired = pumpManager!!.getPairedPumpAddresses().isNotEmpty()
|
||||
_pairedStateUIFlow.value = paired
|
||||
|
||||
setDriverState(DriverState.Disconnected)
|
||||
|
||||
// NOTE: EventInitializationChanged is sent in getPumpStatus() .
|
||||
} catch (e: CancellationException) {
|
||||
aapsLogger.info(LTag.PUMP, "combov2 driver start cancelled")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
// Cancel any ongoing background coroutines. This includes an ongoing
|
||||
// unfinished initialization that still waits for the user to grant
|
||||
// Bluetooth permissions.
|
||||
pumpCoroutineScope.cancel()
|
||||
aapsLogger.info(LTag.PUMP, "Stopping combov2 driver")
|
||||
|
||||
runBlocking {
|
||||
// Cancel any ongoing background coroutines. This includes an ongoing
|
||||
// unfinished initialization that still waits for the user to grant
|
||||
// Bluetooth permissions. Also join to wait for the coroutines to
|
||||
// finish. Otherwise, race conditions can occur, for example, when
|
||||
// a coroutine tries to access bluetoothInterface right after it
|
||||
// was torn down below.
|
||||
pumpCoroutineScopeJob.cancelAndJoin()
|
||||
|
||||
// Normally this should not happen, but to be safe,
|
||||
// make sure any running pump instance is disconnected.
|
||||
pump?.disconnect()
|
||||
|
@ -353,7 +390,13 @@ class ComboV2Plugin @Inject constructor (
|
|||
rxBus.send(EventInitializationChanged())
|
||||
initializationChangedEventSent = false
|
||||
|
||||
// The old job and scope were completed. We need new ones.
|
||||
pumpCoroutineScopeJob = SupervisorJob()
|
||||
pumpCoroutineScope = CoroutineScope(Dispatchers.Default + pumpCoroutineScopeJob)
|
||||
|
||||
super.onStop()
|
||||
|
||||
aapsLogger.info(LTag.PUMP, "combov2 driver stopped")
|
||||
}
|
||||
|
||||
override fun preprocessPreferences(preferenceFragment: PreferenceFragmentCompat) {
|
||||
|
@ -509,18 +552,18 @@ class ComboV2Plugin @Inject constructor (
|
|||
}
|
||||
|
||||
try {
|
||||
runBlocking {
|
||||
pump = pumpManager?.acquirePump(bluetoothAddress, activeBasalProfile) { event -> handlePumpEvent(event) }
|
||||
val curPumpManager = pumpManager ?: throw Error("Could not get pump manager; this should not happen. Please report this as a bug.")
|
||||
|
||||
val acquiredPump = runBlocking {
|
||||
curPumpManager.acquirePump(bluetoothAddress, activeBasalProfile) { event -> handlePumpEvent(event) }
|
||||
}
|
||||
|
||||
if (pump == null) {
|
||||
aapsLogger.error(LTag.PUMP, "Could not get pump instance - pump state store may be corrupted")
|
||||
unpairDueToPumpDataError()
|
||||
return
|
||||
}
|
||||
pump = acquiredPump
|
||||
|
||||
_bluetoothAddressUIFlow.value = bluetoothAddress.toString()
|
||||
_serialNumberUIFlow.value = pumpManager!!.getPumpID(bluetoothAddress)
|
||||
_serialNumberUIFlow.value = curPumpManager.getPumpID(bluetoothAddress)
|
||||
|
||||
rxBus.send(EventDismissNotification(Notification.BLUETOOTH_NOT_ENABLED))
|
||||
|
||||
// Erase any display frame that may be left over from a previous connection.
|
||||
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
|
||||
|
@ -528,7 +571,7 @@ class ComboV2Plugin @Inject constructor (
|
|||
|
||||
stateAndStatusFlowsDeferred = pumpCoroutineScope.async {
|
||||
coroutineScope {
|
||||
pump!!.stateFlow
|
||||
acquiredPump.stateFlow
|
||||
.onEach { pumpState ->
|
||||
val driverState = when (pumpState) {
|
||||
// The Disconnected pump state is ignored, since the Disconnected
|
||||
|
@ -549,7 +592,7 @@ class ComboV2Plugin @Inject constructor (
|
|||
setDriverState(driverState)
|
||||
}
|
||||
.launchIn(this)
|
||||
pump!!.statusFlow
|
||||
acquiredPump.statusFlow
|
||||
.onEach { newPumpStatus ->
|
||||
if (newPumpStatus == null)
|
||||
return@onEach
|
||||
|
@ -571,7 +614,7 @@ class ComboV2Plugin @Inject constructor (
|
|||
rxBus.send(EventRefreshOverview("ComboV2 pump status updated"))
|
||||
}
|
||||
.launchIn(this)
|
||||
pump!!.lastBolusFlow
|
||||
acquiredPump.lastBolusFlow
|
||||
.onEach { lastBolus ->
|
||||
if (lastBolus == null)
|
||||
return@onEach
|
||||
|
@ -579,7 +622,7 @@ class ComboV2Plugin @Inject constructor (
|
|||
_lastBolusUIFlow.value = lastBolus
|
||||
}
|
||||
.launchIn(this)
|
||||
pump!!.currentTbrFlow
|
||||
acquiredPump.currentTbrFlow
|
||||
.onEach { currentTbr ->
|
||||
_currentTbrUIFlow.value = currentTbr
|
||||
}
|
||||
|
@ -587,7 +630,7 @@ class ComboV2Plugin @Inject constructor (
|
|||
}
|
||||
}
|
||||
|
||||
setupUiFlows()
|
||||
setupUiFlows(acquiredPump)
|
||||
|
||||
////
|
||||
// The actual connect procedure begins here.
|
||||
|
@ -696,6 +739,12 @@ class ComboV2Plugin @Inject constructor (
|
|||
executePendingDisconnect()
|
||||
}
|
||||
}
|
||||
} catch (_: BluetoothNotEnabledException) {
|
||||
uiInteraction.addNotification(
|
||||
Notification.BLUETOOTH_NOT_ENABLED,
|
||||
text = rh.gs(info.nightscout.core.ui.R.string.ble_not_enabled),
|
||||
level = Notification.INFO
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
aapsLogger.error(LTag.PUMP, "Connection failure: $e")
|
||||
ToastUtils.showToastInUiThread(context, rh.gs(R.string.combov2_could_not_connect))
|
||||
|
@ -766,6 +815,8 @@ class ComboV2Plugin @Inject constructor (
|
|||
}
|
||||
}
|
||||
|
||||
val acquiredPump = getAcquiredPump()
|
||||
|
||||
rxBus.send(EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED))
|
||||
rxBus.send(EventDismissNotification(Notification.FAILED_UPDATE_PROFILE))
|
||||
|
||||
|
@ -777,7 +828,7 @@ class ComboV2Plugin @Inject constructor (
|
|||
runBlocking {
|
||||
try {
|
||||
executeCommand {
|
||||
if (pump!!.setBasalProfile(requestedBasalProfile)) {
|
||||
if (acquiredPump.setBasalProfile(requestedBasalProfile)) {
|
||||
aapsLogger.debug(LTag.PUMP, "Basal profiles are different; new profile set")
|
||||
activeBasalProfile = requestedBasalProfile
|
||||
updateBaseBasalRateUI()
|
||||
|
@ -872,7 +923,6 @@ class ComboV2Plugin @Inject constructor (
|
|||
pumpSync.insertTherapyEventIfNewWithTimestamp(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
type = DetailedBolusInfo.EventType.INSULIN_CHANGE,
|
||||
note = rh.gs(R.string.combov2_note_reservoir_change),
|
||||
pumpId = null,
|
||||
pumpType = PumpType.ACCU_CHEK_COMBO,
|
||||
pumpSerial = serialNumber()
|
||||
|
@ -897,7 +947,6 @@ class ComboV2Plugin @Inject constructor (
|
|||
pumpSync.insertTherapyEventIfNewWithTimestamp(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
type = DetailedBolusInfo.EventType.PUMP_BATTERY_CHANGE,
|
||||
note = rh.gs(R.string.combov2_note_battery_change),
|
||||
pumpId = null,
|
||||
pumpType = PumpType.ACCU_CHEK_COMBO,
|
||||
pumpSerial = serialNumber()
|
||||
|
@ -927,6 +976,8 @@ class ComboV2Plugin @Inject constructor (
|
|||
// (Also, a zero insulin value makes no sense when bolusing.)
|
||||
require((detailedBolusInfo.insulin > 0) && (detailedBolusInfo.carbs <= 0.0)) { detailedBolusInfo.toString() }
|
||||
|
||||
val acquiredPump = getAcquiredPump()
|
||||
|
||||
val requestedBolusAmount = detailedBolusInfo.insulin.iuToCctlBolus()
|
||||
val bolusReason = when (detailedBolusInfo.bolusType) {
|
||||
DetailedBolusInfo.BolusType.NORMAL -> ComboCtlPump.StandardBolusReason.NORMAL
|
||||
|
@ -960,7 +1011,7 @@ class ComboV2Plugin @Inject constructor (
|
|||
)
|
||||
|
||||
val bolusProgressJob = pumpCoroutineScope.launch {
|
||||
pump!!.bolusDeliveryProgressFlow
|
||||
acquiredPump.bolusDeliveryProgressFlow
|
||||
.collect { progressReport ->
|
||||
when (progressReport.stage) {
|
||||
is RTCommandProgressStage.DeliveringBolus -> {
|
||||
|
@ -983,14 +1034,16 @@ class ComboV2Plugin @Inject constructor (
|
|||
// Run the delivery in a sub-coroutine to be able
|
||||
// to cancel it via stopBolusDelivering().
|
||||
val newBolusJob = pumpCoroutineScope.async {
|
||||
// Store a local reference to the Pump instance. "pump"
|
||||
// is set to null in case of an error, because then,
|
||||
// disconnectInternal() is called (which sets pump to null).
|
||||
// However, we still need to access the last delivered bolus
|
||||
// from the pump's lastBolusFlow, even if an error happened.
|
||||
// Solve this by storing this reference and accessing the
|
||||
// lastBolusFlow through it.
|
||||
val acquiredPump = pump!!
|
||||
// NOTE: Above, we take a local reference to the acquired Pump instance,
|
||||
// with a check that throws an exception in case the "pump" member is
|
||||
// null. This local reference is particularly important inside this
|
||||
// coroutine, because the "pump" member is set to null in case of an
|
||||
// error or other disconnect reason (see disconnectInternal()). However,
|
||||
// we still need to access the last delivered bolus inside this coroutine
|
||||
// from the pump's lastBolusFlow, even if an error happened. Accessing
|
||||
// it through the "pump" member would then result in an NPE. This is
|
||||
// solved by instead accessing the lastBolusFlow through the local
|
||||
// "acquiredPump" reference.
|
||||
|
||||
try {
|
||||
executeCommand {
|
||||
|
@ -1170,11 +1223,13 @@ class ComboV2Plugin @Inject constructor (
|
|||
return
|
||||
}
|
||||
|
||||
val acquiredPump = getAcquiredPump()
|
||||
|
||||
runBlocking {
|
||||
try {
|
||||
executeCommand {
|
||||
|
||||
val tbrComment = when (pump!!.setTbr(percentage, durationInMinutes, tbrType, force100Percent)) {
|
||||
val tbrComment = when (acquiredPump.setTbr(percentage, durationInMinutes, tbrType, force100Percent)) {
|
||||
ComboCtlPump.SetTbrOutcome.SET_NORMAL_TBR ->
|
||||
rh.gs(R.string.combov2_setting_tbr_succeeded)
|
||||
ComboCtlPump.SetTbrOutcome.SET_EMULATED_100_TBR ->
|
||||
|
@ -1327,8 +1382,9 @@ class ComboV2Plugin @Inject constructor (
|
|||
|
||||
override fun serialNumber(): String {
|
||||
val bluetoothAddress = getBluetoothAddress()
|
||||
return if ((bluetoothAddress != null) && (pumpManager != null))
|
||||
pumpManager!!.getPumpID(bluetoothAddress)
|
||||
val curPumpManager = pumpManager
|
||||
return if ((bluetoothAddress != null) && (curPumpManager != null))
|
||||
curPumpManager.getPumpID(bluetoothAddress)
|
||||
else
|
||||
rh.gs(R.string.combov2_not_paired)
|
||||
}
|
||||
|
@ -1396,6 +1452,7 @@ class ComboV2Plugin @Inject constructor (
|
|||
|
||||
override fun loadTDDs(): PumpEnactResult {
|
||||
val pumpEnactResult = PumpEnactResult(injector)
|
||||
val acquiredPump = getAcquiredPump()
|
||||
|
||||
runBlocking {
|
||||
try {
|
||||
|
@ -1403,7 +1460,7 @@ class ComboV2Plugin @Inject constructor (
|
|||
val tddMap = mutableMapOf<Long, Int>()
|
||||
|
||||
executeCommand {
|
||||
val tddHistory = pump!!.fetchTDDHistory()
|
||||
val tddHistory = acquiredPump.fetchTDDHistory()
|
||||
|
||||
tddHistory
|
||||
.filter { it.totalDailyAmount >= 1 }
|
||||
|
@ -1544,15 +1601,24 @@ class ComboV2Plugin @Inject constructor (
|
|||
context, config, aapsLogger, androidPermission,
|
||||
permissionsToCheckFor = listOf("android.permission.BLUETOOTH_CONNECT")
|
||||
) {
|
||||
pumpManager?.pairWithNewPump(discoveryDuration) { newPumpAddress, previousAttemptFailed ->
|
||||
aapsLogger.info(
|
||||
LTag.PUMP,
|
||||
"New pairing PIN request from Combo pump with Bluetooth " +
|
||||
"address $newPumpAddress (previous attempt failed: $previousAttemptFailed)"
|
||||
)
|
||||
_previousPairingAttemptFailedFlow.value = previousAttemptFailed
|
||||
newPINChannel.receive()
|
||||
} ?: throw IllegalStateException("Attempting to access uninitialized pump manager")
|
||||
try {
|
||||
pumpManager?.pairWithNewPump(discoveryDuration) { newPumpAddress, previousAttemptFailed ->
|
||||
aapsLogger.info(
|
||||
LTag.PUMP,
|
||||
"New pairing PIN request from Combo pump with Bluetooth " +
|
||||
"address $newPumpAddress (previous attempt failed: $previousAttemptFailed)"
|
||||
)
|
||||
_previousPairingAttemptFailedFlow.value = previousAttemptFailed
|
||||
newPINChannel.receive()
|
||||
} ?: throw IllegalStateException("Attempting to access uninitialized pump manager")
|
||||
} catch (e: BluetoothNotEnabledException) {
|
||||
// If Bluetooth is turned off during pairing, show a toaster message.
|
||||
// Notifications on the AAPS overview fragment are not useful here
|
||||
// because the pairing activity obscures that fragment. So, instead,
|
||||
// alert the user by showing the notification via the toaster.
|
||||
ToastUtils.errorToast(context, info.nightscout.core.ui.R.string.ble_not_enabled)
|
||||
ComboCtlPumpManager.PairingResult.ExceptionDuringPairing(e)
|
||||
}
|
||||
}
|
||||
|
||||
if (pairingResult !is ComboCtlPumpManager.PairingResult.Success)
|
||||
|
@ -1716,11 +1782,11 @@ class ComboV2Plugin @Inject constructor (
|
|||
|
||||
/*** Misc private functions ***/
|
||||
|
||||
private fun setupUiFlows() {
|
||||
private fun setupUiFlows(acquiredPump: ComboCtlPump) {
|
||||
pumpUIFlowsDeferred = pumpCoroutineScope.async {
|
||||
try {
|
||||
coroutineScope {
|
||||
pump!!.connectProgressFlow
|
||||
acquiredPump.connectProgressFlow
|
||||
.onEach { progressReport ->
|
||||
val description = when (val progStage = progressReport.stage) {
|
||||
is BasicProgressStage.EstablishingBtConnection ->
|
||||
|
@ -1738,7 +1804,7 @@ class ComboV2Plugin @Inject constructor (
|
|||
}
|
||||
.launchIn(this)
|
||||
|
||||
pump!!.setDateTimeProgressFlow
|
||||
acquiredPump.setDateTimeProgressFlow
|
||||
.onEach { progressReport ->
|
||||
val description = when (progressReport.stage) {
|
||||
RTCommandProgressStage.SettingDateTimeHour,
|
||||
|
@ -1755,7 +1821,7 @@ class ComboV2Plugin @Inject constructor (
|
|||
}
|
||||
.launchIn(this)
|
||||
|
||||
pump!!.getBasalProfileFlow
|
||||
acquiredPump.getBasalProfileFlow
|
||||
.onEach { progressReport ->
|
||||
val description = when (val stage = progressReport.stage) {
|
||||
is RTCommandProgressStage.GettingBasalProfile ->
|
||||
|
@ -1769,7 +1835,7 @@ class ComboV2Plugin @Inject constructor (
|
|||
}
|
||||
.launchIn(this)
|
||||
|
||||
pump!!.setBasalProfileFlow
|
||||
acquiredPump.setBasalProfileFlow
|
||||
.onEach { progressReport ->
|
||||
val description = when (val stage = progressReport.stage) {
|
||||
is RTCommandProgressStage.SettingBasalProfile ->
|
||||
|
@ -1783,7 +1849,7 @@ class ComboV2Plugin @Inject constructor (
|
|||
}
|
||||
.launchIn(this)
|
||||
|
||||
pump!!.bolusDeliveryProgressFlow
|
||||
acquiredPump.bolusDeliveryProgressFlow
|
||||
.onEach { progressReport ->
|
||||
val description = when (val stage = progressReport.stage) {
|
||||
is RTCommandProgressStage.DeliveringBolus ->
|
||||
|
@ -1801,7 +1867,7 @@ class ComboV2Plugin @Inject constructor (
|
|||
}
|
||||
.launchIn(this)
|
||||
|
||||
pump!!.parsedDisplayFrameFlow
|
||||
acquiredPump.parsedDisplayFrameFlow
|
||||
.onEach { parsedDisplayFrame ->
|
||||
_displayFrameUIFlow.emit(
|
||||
parsedDisplayFrame?.displayFrame ?: NullDisplayFrame
|
||||
|
@ -2003,7 +2069,11 @@ class ComboV2Plugin @Inject constructor (
|
|||
|
||||
// It makes no sense to reach this location with pump
|
||||
// being null due to the checks above.
|
||||
assert(pump != null)
|
||||
val pumpToDisconnect = pump
|
||||
if (pumpToDisconnect == null) {
|
||||
aapsLogger.error(LTag.PUMP, "Current pump is already null")
|
||||
return
|
||||
}
|
||||
|
||||
// Run these operations in a coroutine to be able to wait
|
||||
// until the disconnect really completes and the UI flows
|
||||
|
@ -2041,17 +2111,17 @@ class ComboV2Plugin @Inject constructor (
|
|||
// the Pump.disconnect() call shuts down the RFCOMM socket,
|
||||
// making all send/receive calls fail.
|
||||
|
||||
if (pump!!.stateFlow.value == ComboCtlPump.State.Connecting) {
|
||||
if (pumpToDisconnect.stateFlow.value == ComboCtlPump.State.Connecting) {
|
||||
// Case #1 from above
|
||||
aapsLogger.debug(LTag.PUMP, "Cancelling ongoing connect attempt")
|
||||
connectionSetupJob?.cancel()
|
||||
pump?.disconnect()
|
||||
pumpToDisconnect.disconnect()
|
||||
connectionSetupJob?.join()
|
||||
} else {
|
||||
// Case #2 from above
|
||||
aapsLogger.debug(LTag.PUMP, "Disconnecting Combo (if not disconnected already by a cancelling request)")
|
||||
connectionSetupJob?.cancelAndJoin()
|
||||
pump?.disconnect()
|
||||
pumpToDisconnect.disconnect()
|
||||
}
|
||||
|
||||
aapsLogger.debug(LTag.PUMP, "Combo disconnected; cancelling UI flows coroutine")
|
||||
|
@ -2284,6 +2354,8 @@ class ComboV2Plugin @Inject constructor (
|
|||
private fun getBluetoothAddress(): ComboCtlBluetoothAddress? =
|
||||
pumpManager?.getPairedPumpAddresses()?.firstOrNull()
|
||||
|
||||
private fun getAcquiredPump() = pump ?: throw Error("There is no currently acquired pump; this should not happen. Please report this as a bug.")
|
||||
|
||||
private fun isDisconnected() =
|
||||
when (driverStateFlow.value) {
|
||||
DriverState.NotInitialized,
|
||||
|
|
|
@ -3,6 +3,7 @@ package info.nightscout.pump.combov2
|
|||
import android.content.Context
|
||||
import android.os.Build
|
||||
import info.nightscout.comboctl.android.AndroidBluetoothPermissionException
|
||||
import info.nightscout.comboctl.base.ComboException
|
||||
import info.nightscout.comboctl.main.BasalProfile
|
||||
import info.nightscout.comboctl.main.NUM_COMBO_BASAL_PROFILE_FACTORS
|
||||
import info.nightscout.interfaces.AndroidPermission
|
||||
|
@ -32,7 +33,37 @@ fun AAPSProfile.toComboCtlBasalProfile(): BasalProfile {
|
|||
return BasalProfile(factors)
|
||||
}
|
||||
|
||||
suspend fun <T> runWithPermissionCheck(
|
||||
internal class RetryPermissionCheckException : ComboException("retry permission check")
|
||||
|
||||
// Utility function to perform Android permission checks before running a block.
|
||||
// If the permissions are not given, wait for a little while, then retry.
|
||||
// This is needed for AAPS<->combov2 integration, since AAPS can start combov2
|
||||
// even _before_ the user granted AAPS BLUETOOTH_CONNECT etc. permissions.
|
||||
//
|
||||
// permissionsToCheckFor is a collection of permissions strings like
|
||||
// Manifest.permission.BLUETOOTH_SCAN. The function goes through the collection,
|
||||
// and checks each and every permission to see if they have all been granted.
|
||||
// Only if all have been granted will the block be executed.
|
||||
//
|
||||
// It is possible that within the block, some additional permission checks
|
||||
// are performed - in particular, these can be checks for permissions that
|
||||
// weren't part of the permissionsToCheckFor collection. If such a permission
|
||||
// is not granted, the block can throw AndroidBluetoothPermissionException.
|
||||
// That exception also specifies what exact permissions haven't been granted
|
||||
// (yet). runWithPermissionCheck() then adds these missing permissions to
|
||||
// permissionsToCheckFor, and tries its permission check again, this time
|
||||
// with these extra permissions included. That way, a failed permission
|
||||
// check within the block does not break anything, and instead, these
|
||||
// permissions too are re-checked by the same logic as the one that looks
|
||||
// at the initially specified permissions.
|
||||
//
|
||||
// Additionally, the block might perform other checks that are not directly
|
||||
// permissions but related to them. One example is a check to see if the
|
||||
// Bluetooth adapter is enabled in addition to checking for Bluetooth
|
||||
// permissions. When such custom checks fail, they can throw
|
||||
// RetryPermissionCheckException to inform this function that it should
|
||||
// retry its run, just as if a permission hadn't been granted.
|
||||
internal suspend fun <T> runWithPermissionCheck(
|
||||
context: Context,
|
||||
config: Config,
|
||||
aapsLogger: AAPSLogger,
|
||||
|
@ -53,12 +84,20 @@ suspend fun <T> runWithPermissionCheck(
|
|||
}
|
||||
|
||||
if (notAllPermissionsGranted) {
|
||||
delay(1000) // Wait a little bit before retrying to avoid 100% CPU usage
|
||||
// Wait a little bit before retrying to avoid 100% CPU usage.
|
||||
// It is currently unknown if there is a way to instead get
|
||||
// some sort of event from Android to inform that the permissions
|
||||
// have now been granted, so we have to resort to polling,
|
||||
// at least for now.
|
||||
delay(1000)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return block.invoke()
|
||||
} catch (e: RetryPermissionCheckException) {
|
||||
// See the above delay() call, which fulfills the exact same purpose.
|
||||
delay(1000)
|
||||
} catch (e: AndroidBluetoothPermissionException) {
|
||||
permissions = permissionsToCheckFor union e.missingPermissions
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue