combov2: Use extra internal SP as the main pump state store
Signed-off-by: Carlos Rafael Giani <crg7475@mailbox.org>
This commit is contained in:
parent
a9690dcbc9
commit
69f085a83c
4 changed files with 476 additions and 166 deletions
|
@ -0,0 +1,233 @@
|
||||||
|
package info.nightscout.pump.combov2
|
||||||
|
|
||||||
|
import info.nightscout.comboctl.base.BluetoothAddress
|
||||||
|
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.Tbr
|
||||||
|
import info.nightscout.comboctl.base.toBluetoothAddress
|
||||||
|
import info.nightscout.comboctl.base.toCipher
|
||||||
|
import info.nightscout.comboctl.base.toNonce
|
||||||
|
import info.nightscout.shared.sharedPreferences.SP
|
||||||
|
import info.nightscout.shared.sharedPreferences.SPDelegateInt
|
||||||
|
import info.nightscout.shared.sharedPreferences.SPDelegateLong
|
||||||
|
import info.nightscout.shared.sharedPreferences.SPDelegateString
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
import kotlinx.datetime.UtcOffset
|
||||||
|
import kotlin.reflect.KClassifier
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special pump state store that mainly uses the internalSP, but also is able to sync up the AAPS main SP with that internal SP.
|
||||||
|
*
|
||||||
|
* This pump state store solves a problem: What if the user already paired AAPS with a Combo, and then imports and old AAPS
|
||||||
|
* settings file, intending to just restore other settings like the basal profile, but does _not_ intend to import an old
|
||||||
|
* pump state? After all, if we write a pump state to the AAPS main SP, the state values will also be written into a settings
|
||||||
|
* file when exporting said settings. If we just relied on the main AAPS SP, the current pump state - including pairing info
|
||||||
|
* like the PC and CP keys - would be overwritten with the old ones from the imported settings, and the user would have to
|
||||||
|
* unnecessarily re-pair the Combo every time settings get imported.
|
||||||
|
*
|
||||||
|
* The solution is for this driver to _not_ primarily use the AAPS main SP. Instead, it uses its own internal SP. That SP
|
||||||
|
* is exclusively used by this driver. But, a copy of the pump state values are stored in the AAPS main SP, and kept in
|
||||||
|
* sync with the contents of the internal SP.
|
||||||
|
*
|
||||||
|
* When a new pump state is created, the invariant values (CP/PC keys etc.) are written in both the AAPS main SP and the
|
||||||
|
* internal SP, along with the initial nonce. During operation, the driver will update the nonce value of the internal SP.
|
||||||
|
* If TBR states are changed, then these changes are stored in the internal SP. Callers can use [copyVariantValuesToAAPSMainSP]
|
||||||
|
* to update the corresponding values in the AAPS main SP to keep the two SPs in sync.
|
||||||
|
*
|
||||||
|
* There are a couple of special situations:
|
||||||
|
*
|
||||||
|
* 1. There is no pump state in the internal SP, and there is no pump state in the AAPS main SP. This is the unpaired state.
|
||||||
|
* 2. There is no pump state in the internal SP, but there is one in the AAPS main SP. This typically happens when the
|
||||||
|
* user (re)installed AAPS, and immediately after installing, imported AAPS settings. Callers are then supposed to call
|
||||||
|
* [copyAllValuesFromAAPSMainSP] to import the pump state from the AAPS main SP over to the internal SP and continue to
|
||||||
|
* work with that state.
|
||||||
|
* 3. There is a pump state, and there is also one in the AAPS main SP. The latter one is then ignored. A pump state in the
|
||||||
|
* AAPS main SP solely and only exists to be able to export/import pump states. It is not used for actual pump operations.
|
||||||
|
* In particular, if - as mentioned above - a pump is already paired, and the user imports settings, this logic prevents
|
||||||
|
* the current pump state to be overwritten.
|
||||||
|
*/
|
||||||
|
class AAPSPumpStateStore(
|
||||||
|
private val aapsMainSP: SP,
|
||||||
|
private val internalSP: InternalSP
|
||||||
|
) : PumpStateStore {
|
||||||
|
private var btAddress: String
|
||||||
|
by SPDelegateString(internalSP, PreferenceKeys.BT_ADDRESS_KEY.str, "")
|
||||||
|
|
||||||
|
// The nonce is updated with commit instead of apply to make sure
|
||||||
|
// is atomically written to storage synchronously, minimizing
|
||||||
|
// the likelihood that it could be lost due to app crashes etc.
|
||||||
|
// It is very important to not lose the nonce, hence that choice.
|
||||||
|
private var nonceString: String
|
||||||
|
by SPDelegateString(internalSP, PreferenceKeys.NONCE_KEY.str, Nonce.nullNonce().toString(), commit = true)
|
||||||
|
|
||||||
|
private var cpCipherString: String
|
||||||
|
by SPDelegateString(internalSP, PreferenceKeys.CP_CIPHER_KEY.str, "")
|
||||||
|
private var pcCipherString: String
|
||||||
|
by SPDelegateString(internalSP, PreferenceKeys.PC_CIPHER_KEY.str, "")
|
||||||
|
private var keyResponseAddressInt: Int
|
||||||
|
by SPDelegateInt(internalSP, PreferenceKeys.KEY_RESPONSE_ADDRESS_KEY.str, 0)
|
||||||
|
private var pumpID: String
|
||||||
|
by SPDelegateString(internalSP, PreferenceKeys.PUMP_ID_KEY.str, "")
|
||||||
|
private var tbrTimestamp: Long
|
||||||
|
by SPDelegateLong(internalSP, PreferenceKeys.TBR_TIMESTAMP_KEY.str, 0)
|
||||||
|
private var tbrPercentage: Int
|
||||||
|
by SPDelegateInt(internalSP, PreferenceKeys.TBR_PERCENTAGE_KEY.str, 0)
|
||||||
|
private var tbrDuration: Int
|
||||||
|
by SPDelegateInt(internalSP, PreferenceKeys.TBR_DURATION_KEY.str, 0)
|
||||||
|
private var tbrType: String
|
||||||
|
by SPDelegateString(internalSP, PreferenceKeys.TBR_TYPE_KEY.str, "")
|
||||||
|
private var utcOffsetSeconds: Int
|
||||||
|
by SPDelegateInt(internalSP, PreferenceKeys.UTC_OFFSET_KEY.str, 0)
|
||||||
|
|
||||||
|
override fun createPumpState(
|
||||||
|
pumpAddress: BluetoothAddress,
|
||||||
|
invariantPumpData: InvariantPumpData,
|
||||||
|
utcOffset: UtcOffset,
|
||||||
|
tbrState: CurrentTbrState
|
||||||
|
) {
|
||||||
|
internalSP.edit(commit = true) {
|
||||||
|
putString(PreferenceKeys.BT_ADDRESS_KEY.str, pumpAddress.toString().uppercase())
|
||||||
|
putString(PreferenceKeys.CP_CIPHER_KEY.str, invariantPumpData.clientPumpCipher.toString())
|
||||||
|
putString(PreferenceKeys.PC_CIPHER_KEY.str, invariantPumpData.pumpClientCipher.toString())
|
||||||
|
putInt(PreferenceKeys.KEY_RESPONSE_ADDRESS_KEY.str, invariantPumpData.keyResponseAddress.toInt() and 0xFF)
|
||||||
|
putString(PreferenceKeys.PUMP_ID_KEY.str, invariantPumpData.pumpID)
|
||||||
|
putLong(PreferenceKeys.TBR_TIMESTAMP_KEY.str, if (tbrState is CurrentTbrState.TbrStarted) tbrState.tbr.timestamp.epochSeconds else -1)
|
||||||
|
putInt(PreferenceKeys.TBR_PERCENTAGE_KEY.str, if (tbrState is CurrentTbrState.TbrStarted) tbrState.tbr.percentage else -1)
|
||||||
|
putInt(PreferenceKeys.TBR_DURATION_KEY.str, if (tbrState is CurrentTbrState.TbrStarted) tbrState.tbr.durationInMinutes else -1)
|
||||||
|
putString(PreferenceKeys.TBR_TYPE_KEY.str, if (tbrState is CurrentTbrState.TbrStarted) tbrState.tbr.type.stringId else "")
|
||||||
|
putInt(PreferenceKeys.UTC_OFFSET_KEY.str, utcOffset.totalSeconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
copyAllValuesToAAPSMainSP(commit = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deletePumpState(pumpAddress: BluetoothAddress): Boolean {
|
||||||
|
val hasState = internalSP.contains(PreferenceKeys.NONCE_KEY.str)
|
||||||
|
|
||||||
|
internalSP.edit(commit = true) {
|
||||||
|
for (keys in PreferenceKeys.values())
|
||||||
|
remove(keys.str)
|
||||||
|
}
|
||||||
|
|
||||||
|
aapsMainSP.edit(commit = true) {
|
||||||
|
for (keys in PreferenceKeys.values())
|
||||||
|
remove(keys.str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasState
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasPumpState(pumpAddress: BluetoothAddress): Boolean =
|
||||||
|
internalSP.contains(PreferenceKeys.NONCE_KEY.str)
|
||||||
|
|
||||||
|
override fun getAvailablePumpStateAddresses(): Set<BluetoothAddress> =
|
||||||
|
if (btAddress.isBlank()) setOf() else setOf(btAddress.toBluetoothAddress())
|
||||||
|
|
||||||
|
override fun getInvariantPumpData(pumpAddress: BluetoothAddress) = InvariantPumpData(
|
||||||
|
clientPumpCipher = cpCipherString.toCipher(),
|
||||||
|
pumpClientCipher = pcCipherString.toCipher(),
|
||||||
|
keyResponseAddress = keyResponseAddressInt.toByte(),
|
||||||
|
pumpID = pumpID
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getCurrentTxNonce(pumpAddress: BluetoothAddress) = nonceString.toNonce()
|
||||||
|
|
||||||
|
override fun setCurrentTxNonce(pumpAddress: BluetoothAddress, currentTxNonce: Nonce) {
|
||||||
|
nonceString = currentTxNonce.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCurrentUtcOffset(pumpAddress: BluetoothAddress) =
|
||||||
|
UtcOffset(seconds = utcOffsetSeconds)
|
||||||
|
|
||||||
|
override fun setCurrentUtcOffset(pumpAddress: BluetoothAddress, utcOffset: UtcOffset) {
|
||||||
|
utcOffsetSeconds = utcOffset.totalSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCurrentTbrState(pumpAddress: BluetoothAddress) =
|
||||||
|
if (tbrTimestamp >= 0)
|
||||||
|
CurrentTbrState.TbrStarted(Tbr(
|
||||||
|
timestamp = Instant.fromEpochSeconds(tbrTimestamp),
|
||||||
|
percentage = tbrPercentage,
|
||||||
|
durationInMinutes = tbrDuration,
|
||||||
|
type = Tbr.Type.fromStringId(tbrType)!!
|
||||||
|
))
|
||||||
|
else
|
||||||
|
CurrentTbrState.NoTbrOngoing
|
||||||
|
|
||||||
|
|
||||||
|
override fun setCurrentTbrState(pumpAddress: BluetoothAddress, currentTbrState: CurrentTbrState) {
|
||||||
|
when (currentTbrState) {
|
||||||
|
is CurrentTbrState.TbrStarted -> {
|
||||||
|
tbrTimestamp = currentTbrState.tbr.timestamp.epochSeconds
|
||||||
|
tbrPercentage = currentTbrState.tbr.percentage
|
||||||
|
tbrDuration = currentTbrState.tbr.durationInMinutes
|
||||||
|
tbrType = currentTbrState.tbr.type.stringId
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
tbrTimestamp = -1
|
||||||
|
tbrPercentage = -1
|
||||||
|
tbrDuration = -1
|
||||||
|
tbrType = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies only those pump state values from the internal SP to the AAPS main SP which can vary during
|
||||||
|
// pump operations. These are the TBR values, the UTC offset, and the nonce. Users are recommended to
|
||||||
|
// call this after AAPS disconnects the pump.
|
||||||
|
fun copyVariantValuesToAAPSMainSP(commit: Boolean) =
|
||||||
|
copyValuesBetweenSPs(commit, from = internalSP, to = aapsMainSP, arrayOf(
|
||||||
|
PreferenceKeys.NONCE_KEY,
|
||||||
|
PreferenceKeys.TBR_TIMESTAMP_KEY,
|
||||||
|
PreferenceKeys.TBR_PERCENTAGE_KEY,
|
||||||
|
PreferenceKeys.TBR_DURATION_KEY,
|
||||||
|
PreferenceKeys.TBR_TYPE_KEY,
|
||||||
|
PreferenceKeys.UTC_OFFSET_KEY
|
||||||
|
))
|
||||||
|
|
||||||
|
// Copies all pump state values from the AAPS main SP to the internal SP. This is supposed to be
|
||||||
|
// called if the internal SP is empty. That way, a pump state can be imported from AAPS settings files.
|
||||||
|
fun copyAllValuesFromAAPSMainSP(commit: Boolean) =
|
||||||
|
copyValuesBetweenSPs(commit, from = aapsMainSP, to = internalSP, keys = PreferenceKeys.values())
|
||||||
|
|
||||||
|
// Copies all pump state values from the internal SP to the AAPS main SP to. The createPumpState()
|
||||||
|
// function calls this after creating the pump state to ensure both SPs are in sync. Also, this
|
||||||
|
// should be called when the driver starts in case AAPS settings are imported and there is already
|
||||||
|
// a pump state present in the internal SP. Calling this then ensures that the pump state in the
|
||||||
|
// main SP is fully synced up with the one from the internal SP, and does not contain some old
|
||||||
|
// state that is not in use anymore.
|
||||||
|
fun copyAllValuesToAAPSMainSP(commit: Boolean) =
|
||||||
|
copyValuesBetweenSPs(commit, from = internalSP, to = aapsMainSP, keys = PreferenceKeys.values())
|
||||||
|
|
||||||
|
private fun copyValuesBetweenSPs(commit: Boolean, from: SP, to: SP, keys: Array<PreferenceKeys>) {
|
||||||
|
to.edit(commit) {
|
||||||
|
for (key in keys) {
|
||||||
|
if (!from.contains(key.str))
|
||||||
|
continue
|
||||||
|
when (key.type) {
|
||||||
|
Int::class -> putInt(key.str, from.getInt(key.str, 0))
|
||||||
|
Long::class -> putLong(key.str, from.getLong(key.str, 0L))
|
||||||
|
String::class -> putString(key.str, from.getString(key.str, ""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class PreferenceKeys(val str: String, val type: KClassifier) {
|
||||||
|
BT_ADDRESS_KEY("combov2-bt-address-key", String::class),
|
||||||
|
NONCE_KEY("combov2-nonce-key", String::class),
|
||||||
|
CP_CIPHER_KEY("combov2-cp-cipher-key", String::class),
|
||||||
|
PC_CIPHER_KEY("combov2-pc-cipher-key", String::class),
|
||||||
|
KEY_RESPONSE_ADDRESS_KEY("combov2-key-response-address-key", Int::class),
|
||||||
|
PUMP_ID_KEY("combov2-pump-id-key", String::class),
|
||||||
|
TBR_TIMESTAMP_KEY("combov2-tbr-timestamp", Long::class),
|
||||||
|
TBR_PERCENTAGE_KEY("combov2-tbr-percentage", Int::class),
|
||||||
|
TBR_DURATION_KEY("combov2-tbr-duration", Int::class),
|
||||||
|
TBR_TYPE_KEY("combov2-tbr-type", String::class),
|
||||||
|
UTC_OFFSET_KEY("combov2-utc-offset", Int::class);
|
||||||
|
|
||||||
|
override fun toString(): String = str
|
||||||
|
}
|
||||||
|
}
|
|
@ -133,7 +133,17 @@ class ComboV2Plugin @Inject constructor (
|
||||||
|
|
||||||
private val _pumpDescription = PumpDescription()
|
private val _pumpDescription = PumpDescription()
|
||||||
|
|
||||||
private val pumpStateStore = SPPumpStateStore(sp)
|
// The internal SP is the one that will be mainly used by the driver.
|
||||||
|
// The AAPS main SP is updated when the pump state store is created
|
||||||
|
// and when the driver disconnects (to update the nonce value).
|
||||||
|
private val internalSP = InternalSP(
|
||||||
|
context.getSharedPreferences(
|
||||||
|
context.packageName + ".COMBO_PUMP_STATE_STORE",
|
||||||
|
Context.MODE_PRIVATE
|
||||||
|
),
|
||||||
|
context
|
||||||
|
)
|
||||||
|
private val pumpStateStore = AAPSPumpStateStore(aapsMainSP = sp, internalSP = internalSP)
|
||||||
|
|
||||||
// These are initialized in onStart() and torn down in onStop().
|
// These are initialized in onStart() and torn down in onStop().
|
||||||
private var bluetoothInterface: AndroidBluetoothInterface? = null
|
private var bluetoothInterface: AndroidBluetoothInterface? = null
|
||||||
|
@ -236,6 +246,34 @@ class ComboV2Plugin @Inject constructor (
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
|
|
||||||
|
// Check if there is a pump state in the internal SP. If not, try to
|
||||||
|
// copy a pump state from the AAPS main SP. It is possible for example
|
||||||
|
// that AAPS was reinstalled, and the previous settings were imported.
|
||||||
|
// In that case, the internal SP is empty, but there is a pump state
|
||||||
|
// that comes from the settings. We want to restore that pump state
|
||||||
|
// then. If however, there _is_ a pump state in the internal SP, then
|
||||||
|
// we just ignore any state in the main SP. For example, if the user
|
||||||
|
// imports an older AAPS settings file with an old pump state, and a
|
||||||
|
// Combo is already paired with AAPS, then it makes no sense to overwrite
|
||||||
|
// the current pump state with the old one from the imported settings.
|
||||||
|
if (pumpStateStore.getAvailablePumpStateAddresses().isEmpty()) {
|
||||||
|
aapsLogger.info(LTag.PUMP, "There is no pump state in the internal SP; trying to copy a pump state from the main AAPS SP")
|
||||||
|
pumpStateStore.copyAllValuesFromAAPSMainSP(commit = true)
|
||||||
|
val btAddress = pumpStateStore.getAvailablePumpStateAddresses().firstOrNull()
|
||||||
|
if (btAddress == null)
|
||||||
|
aapsLogger.info(LTag.PUMP, "No pump state found in the main AAPS SP; continuing without a pump state (implying that no pump is paired)")
|
||||||
|
else
|
||||||
|
aapsLogger.info(LTag.PUMP, "Pump state found in the main AAPS SP (bluetooth address: $btAddress); continuing with that state")
|
||||||
|
} else {
|
||||||
|
// Copy over the internal SP pump state to the main AAPS SP. If the user
|
||||||
|
// just imported AAPS settings, and said settings contained an old pump
|
||||||
|
// state, then that old pump state is ignored if there is already a
|
||||||
|
// current pump state in the internal SP - but we still need to make sure
|
||||||
|
// the old pump state in the main AAPS SP is replaced by the current one.
|
||||||
|
aapsLogger.debug(LTag.PUMP, "Copying internal SP pump state to main AAPS SP")
|
||||||
|
pumpStateStore.copyAllValuesToAAPSMainSP(commit = false)
|
||||||
|
}
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMP, "Creating bluetooth interface")
|
aapsLogger.debug(LTag.PUMP, "Creating bluetooth interface")
|
||||||
bluetoothInterface = AndroidBluetoothInterface(context)
|
bluetoothInterface = AndroidBluetoothInterface(context)
|
||||||
|
|
||||||
|
@ -624,6 +662,11 @@ class ComboV2Plugin @Inject constructor (
|
||||||
override fun disconnect(reason: String) {
|
override fun disconnect(reason: String) {
|
||||||
aapsLogger.debug(LTag.PUMP, "Disconnecting from Combo; reason: $reason")
|
aapsLogger.debug(LTag.PUMP, "Disconnecting from Combo; reason: $reason")
|
||||||
disconnectInternal(forceDisconnect = false)
|
disconnectInternal(forceDisconnect = false)
|
||||||
|
|
||||||
|
// Sync up the TBR and nonce states in the main AAPS SP. We don't do this all the
|
||||||
|
// time since this is unnecessary waste of resources. It is sufficient to update
|
||||||
|
// those once AAPS is done with the connection.
|
||||||
|
pumpStateStore.copyVariantValuesToAAPSMainSP(commit = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is called when (a) the AAPS watchdog is about to toggle
|
// This is called when (a) the AAPS watchdog is about to toggle
|
||||||
|
|
|
@ -0,0 +1,199 @@
|
||||||
|
package info.nightscout.pump.combov2
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import info.nightscout.shared.SafeParse
|
||||||
|
import info.nightscout.shared.sharedPreferences.SP
|
||||||
|
|
||||||
|
// This is a copy of the AAPS SPImplementation. We keep this to be able
|
||||||
|
// to set up a custom internal SP store for the Combo pump state.
|
||||||
|
class InternalSP(
|
||||||
|
private val sharedPreferences: SharedPreferences,
|
||||||
|
private val context: Context
|
||||||
|
) : SP {
|
||||||
|
@SuppressLint("ApplySharedPref")
|
||||||
|
override fun edit(commit: Boolean, block: SP.Editor.() -> Unit) {
|
||||||
|
val spEdit = sharedPreferences.edit()
|
||||||
|
|
||||||
|
val edit = object : SP.Editor {
|
||||||
|
override fun clear() {
|
||||||
|
spEdit.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove(@StringRes resourceID: Int) {
|
||||||
|
spEdit.remove(context.getString(resourceID))
|
||||||
|
}
|
||||||
|
override fun remove(key: String) {
|
||||||
|
spEdit.remove(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putBoolean(key: String, value: Boolean) {
|
||||||
|
spEdit.putBoolean(key, value)
|
||||||
|
}
|
||||||
|
override fun putBoolean(@StringRes resourceID: Int, value: Boolean) {
|
||||||
|
spEdit.putBoolean(context.getString(resourceID), value)
|
||||||
|
}
|
||||||
|
override fun putDouble(key: String, value: Double) {
|
||||||
|
spEdit.putString(key, value.toString())
|
||||||
|
}
|
||||||
|
override fun putDouble(@StringRes resourceID: Int, value: Double) {
|
||||||
|
spEdit.putString(context.getString(resourceID), value.toString())
|
||||||
|
}
|
||||||
|
override fun putLong(key: String, value: Long) {
|
||||||
|
spEdit.putLong(key, value)
|
||||||
|
}
|
||||||
|
override fun putLong(@StringRes resourceID: Int, value: Long) {
|
||||||
|
spEdit.putLong(context.getString(resourceID), value)
|
||||||
|
}
|
||||||
|
override fun putInt(key: String, value: Int) {
|
||||||
|
spEdit.putInt(key, value)
|
||||||
|
}
|
||||||
|
override fun putInt(@StringRes resourceID: Int, value: Int) {
|
||||||
|
spEdit.putInt(context.getString(resourceID), value)
|
||||||
|
}
|
||||||
|
override fun putString(key: String, value: String) {
|
||||||
|
spEdit.putString(key, value)
|
||||||
|
}
|
||||||
|
override fun putString(@StringRes resourceID: Int, value: String) {
|
||||||
|
spEdit.putString(context.getString(resourceID), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
block(edit)
|
||||||
|
|
||||||
|
if (commit)
|
||||||
|
spEdit.commit()
|
||||||
|
else
|
||||||
|
spEdit.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAll(): Map<String, *> = sharedPreferences.all
|
||||||
|
|
||||||
|
override fun clear() = sharedPreferences.edit().clear().apply()
|
||||||
|
|
||||||
|
override fun contains(key: String): Boolean = sharedPreferences.contains(key)
|
||||||
|
|
||||||
|
override fun contains(resourceId: Int): Boolean = sharedPreferences.contains(context.getString(resourceId))
|
||||||
|
|
||||||
|
override fun remove(resourceID: Int) =
|
||||||
|
sharedPreferences.edit().remove(context.getString(resourceID)).apply()
|
||||||
|
|
||||||
|
override fun remove(key: String) =
|
||||||
|
sharedPreferences.edit().remove(key).apply()
|
||||||
|
|
||||||
|
override fun getString(resourceID: Int, defaultValue: String): String =
|
||||||
|
sharedPreferences.getString(context.getString(resourceID), defaultValue) ?: defaultValue
|
||||||
|
|
||||||
|
override fun getStringOrNull(resourceID: Int, defaultValue: String?): String? =
|
||||||
|
sharedPreferences.getString(context.getString(resourceID), defaultValue) ?: defaultValue
|
||||||
|
|
||||||
|
override fun getStringOrNull(key: String, defaultValue: String?): String? =
|
||||||
|
sharedPreferences.getString(key, defaultValue)
|
||||||
|
|
||||||
|
override fun getString(key: String, defaultValue: String): String =
|
||||||
|
sharedPreferences.getString(key, defaultValue) ?: defaultValue
|
||||||
|
|
||||||
|
override fun getBoolean(resourceID: Int, defaultValue: Boolean): Boolean {
|
||||||
|
return try {
|
||||||
|
sharedPreferences.getBoolean(context.getString(resourceID), defaultValue)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBoolean(key: String, defaultValue: Boolean): Boolean {
|
||||||
|
return try {
|
||||||
|
sharedPreferences.getBoolean(key, defaultValue)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDouble(resourceID: Int, defaultValue: Double): Double =
|
||||||
|
SafeParse.stringToDouble(sharedPreferences.getString(context.getString(resourceID), defaultValue.toString()))
|
||||||
|
|
||||||
|
override fun getDouble(key: String, defaultValue: Double): Double =
|
||||||
|
SafeParse.stringToDouble(sharedPreferences.getString(key, defaultValue.toString()))
|
||||||
|
|
||||||
|
override fun getInt(resourceID: Int, defaultValue: Int): Int {
|
||||||
|
return try {
|
||||||
|
sharedPreferences.getInt(context.getString(resourceID), defaultValue)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
SafeParse.stringToInt(sharedPreferences.getString(context.getString(resourceID), defaultValue.toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getInt(key: String, defaultValue: Int): Int {
|
||||||
|
return try {
|
||||||
|
sharedPreferences.getInt(key, defaultValue)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
SafeParse.stringToInt(sharedPreferences.getString(key, defaultValue.toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLong(resourceID: Int, defaultValue: Long): Long {
|
||||||
|
return try {
|
||||||
|
sharedPreferences.getLong(context.getString(resourceID), defaultValue)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
try {
|
||||||
|
SafeParse.stringToLong(sharedPreferences.getString(context.getString(resourceID), defaultValue.toString()))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLong(key: String, defaultValue: Long): Long {
|
||||||
|
return try {
|
||||||
|
sharedPreferences.getLong(key, defaultValue)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
try {
|
||||||
|
SafeParse.stringToLong(sharedPreferences.getString(key, defaultValue.toString()))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun incLong(resourceID: Int) {
|
||||||
|
val value = getLong(resourceID, 0) + 1L
|
||||||
|
sharedPreferences.edit().putLong(context.getString(resourceID), value).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putBoolean(key: String, value: Boolean) = sharedPreferences.edit().putBoolean(key, value).apply()
|
||||||
|
|
||||||
|
override fun putBoolean(resourceID: Int, value: Boolean) =
|
||||||
|
sharedPreferences.edit().putBoolean(context.getString(resourceID), value).apply()
|
||||||
|
|
||||||
|
override fun putDouble(key: String, value: Double) =
|
||||||
|
sharedPreferences.edit().putString(key, value.toString()).apply()
|
||||||
|
|
||||||
|
override fun putDouble(resourceID: Int, value: Double) {
|
||||||
|
sharedPreferences.edit().putString(context.getString(resourceID), value.toString()).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putLong(key: String, value: Long) =
|
||||||
|
sharedPreferences.edit().putLong(key, value).apply()
|
||||||
|
|
||||||
|
override fun putLong(resourceID: Int, value: Long) =
|
||||||
|
sharedPreferences.edit().putLong(context.getString(resourceID), value).apply()
|
||||||
|
|
||||||
|
override fun putInt(key: String, value: Int) =
|
||||||
|
sharedPreferences.edit().putInt(key, value).apply()
|
||||||
|
|
||||||
|
override fun putInt(resourceID: Int, value: Int) =
|
||||||
|
sharedPreferences.edit().putInt(context.getString(resourceID), value).apply()
|
||||||
|
|
||||||
|
override fun incInt(resourceID: Int) {
|
||||||
|
val value = getInt(resourceID, 0) + 1
|
||||||
|
sharedPreferences.edit().putInt(context.getString(resourceID), value).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putString(resourceID: Int, value: String) =
|
||||||
|
sharedPreferences.edit().putString(context.getString(resourceID), value).apply()
|
||||||
|
|
||||||
|
override fun putString(key: String, value: String) =
|
||||||
|
sharedPreferences.edit().putString(key, value).apply()
|
||||||
|
}
|
|
@ -1,165 +0,0 @@
|
||||||
package info.nightscout.pump.combov2
|
|
||||||
|
|
||||||
import info.nightscout.comboctl.base.BluetoothAddress
|
|
||||||
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.Tbr
|
|
||||||
import info.nightscout.comboctl.base.toBluetoothAddress
|
|
||||||
import info.nightscout.comboctl.base.toCipher
|
|
||||||
import info.nightscout.comboctl.base.toNonce
|
|
||||||
import info.nightscout.shared.sharedPreferences.SP
|
|
||||||
import info.nightscout.shared.sharedPreferences.SPDelegateInt
|
|
||||||
import info.nightscout.shared.sharedPreferences.SPDelegateLong
|
|
||||||
import info.nightscout.shared.sharedPreferences.SPDelegateString
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import kotlinx.datetime.UtcOffset
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AndroidAPS [SP] based pump state store.
|
|
||||||
*
|
|
||||||
* This store is set up to contain a single paired pump. AndroidAPS is not
|
|
||||||
* designed to handle multiple pumps, so this simplification makes sense.
|
|
||||||
* This affects all accessors, which
|
|
||||||
*/
|
|
||||||
class SPPumpStateStore(private val sp: SP) : PumpStateStore {
|
|
||||||
private var btAddress: String
|
|
||||||
by SPDelegateString(sp, BT_ADDRESS_KEY, "")
|
|
||||||
|
|
||||||
// The nonce is updated with commit instead of apply to make sure
|
|
||||||
// is atomically written to storage synchronously, minimizing
|
|
||||||
// the likelihood that it could be lost due to app crashes etc.
|
|
||||||
// It is very important to not lose the nonce, hence that choice.
|
|
||||||
private var nonceString: String
|
|
||||||
by SPDelegateString(sp, NONCE_KEY, Nonce.nullNonce().toString(), commit = true)
|
|
||||||
|
|
||||||
private var cpCipherString: String
|
|
||||||
by SPDelegateString(sp, CP_CIPHER_KEY, "")
|
|
||||||
private var pcCipherString: String
|
|
||||||
by SPDelegateString(sp, PC_CIPHER_KEY, "")
|
|
||||||
private var keyResponseAddressInt: Int
|
|
||||||
by SPDelegateInt(sp, KEY_RESPONSE_ADDRESS_KEY, 0)
|
|
||||||
private var pumpID: String
|
|
||||||
by SPDelegateString(sp, PUMP_ID_KEY, "")
|
|
||||||
private var tbrTimestamp: Long
|
|
||||||
by SPDelegateLong(sp, TBR_TIMESTAMP_KEY, 0)
|
|
||||||
private var tbrPercentage: Int
|
|
||||||
by SPDelegateInt(sp, TBR_PERCENTAGE_KEY, 0)
|
|
||||||
private var tbrDuration: Int
|
|
||||||
by SPDelegateInt(sp, TBR_DURATION_KEY, 0)
|
|
||||||
private var tbrType: String
|
|
||||||
by SPDelegateString(sp, TBR_TYPE_KEY, "")
|
|
||||||
private var utcOffsetSeconds: Int
|
|
||||||
by SPDelegateInt(sp, UTC_OFFSET_KEY, 0)
|
|
||||||
|
|
||||||
override fun createPumpState(
|
|
||||||
pumpAddress: BluetoothAddress,
|
|
||||||
invariantPumpData: InvariantPumpData,
|
|
||||||
utcOffset: UtcOffset,
|
|
||||||
tbrState: CurrentTbrState
|
|
||||||
) {
|
|
||||||
// Write these values via edit() instead of using the delegates
|
|
||||||
// above to be able to write all of them with a single commit.
|
|
||||||
sp.edit(commit = true) {
|
|
||||||
putString(BT_ADDRESS_KEY, pumpAddress.toString().uppercase())
|
|
||||||
putString(CP_CIPHER_KEY, invariantPumpData.clientPumpCipher.toString())
|
|
||||||
putString(PC_CIPHER_KEY, invariantPumpData.pumpClientCipher.toString())
|
|
||||||
putInt(KEY_RESPONSE_ADDRESS_KEY, invariantPumpData.keyResponseAddress.toInt() and 0xFF)
|
|
||||||
putString(PUMP_ID_KEY, invariantPumpData.pumpID)
|
|
||||||
putLong(TBR_TIMESTAMP_KEY, if (tbrState is CurrentTbrState.TbrStarted) tbrState.tbr.timestamp.epochSeconds else -1)
|
|
||||||
putInt(TBR_PERCENTAGE_KEY, if (tbrState is CurrentTbrState.TbrStarted) tbrState.tbr.percentage else -1)
|
|
||||||
putInt(TBR_DURATION_KEY, if (tbrState is CurrentTbrState.TbrStarted) tbrState.tbr.durationInMinutes else -1)
|
|
||||||
putString(TBR_TYPE_KEY, if (tbrState is CurrentTbrState.TbrStarted) tbrState.tbr.type.stringId else "")
|
|
||||||
putInt(UTC_OFFSET_KEY, utcOffset.totalSeconds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deletePumpState(pumpAddress: BluetoothAddress): Boolean {
|
|
||||||
val hasState = sp.contains(NONCE_KEY)
|
|
||||||
|
|
||||||
sp.edit(commit = true) {
|
|
||||||
remove(BT_ADDRESS_KEY)
|
|
||||||
remove(NONCE_KEY)
|
|
||||||
remove(CP_CIPHER_KEY)
|
|
||||||
remove(PC_CIPHER_KEY)
|
|
||||||
remove(KEY_RESPONSE_ADDRESS_KEY)
|
|
||||||
remove(TBR_TIMESTAMP_KEY)
|
|
||||||
remove(TBR_PERCENTAGE_KEY)
|
|
||||||
remove(TBR_DURATION_KEY)
|
|
||||||
remove(TBR_TYPE_KEY)
|
|
||||||
remove(UTC_OFFSET_KEY)
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasState
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hasPumpState(pumpAddress: BluetoothAddress) =
|
|
||||||
sp.contains(NONCE_KEY)
|
|
||||||
|
|
||||||
override fun getAvailablePumpStateAddresses() =
|
|
||||||
if (btAddress.isBlank()) setOf() else setOf(btAddress.toBluetoothAddress())
|
|
||||||
|
|
||||||
override fun getInvariantPumpData(pumpAddress: BluetoothAddress) = InvariantPumpData(
|
|
||||||
clientPumpCipher = cpCipherString.toCipher(),
|
|
||||||
pumpClientCipher = pcCipherString.toCipher(),
|
|
||||||
keyResponseAddress = keyResponseAddressInt.toByte(),
|
|
||||||
pumpID = pumpID
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun getCurrentTxNonce(pumpAddress: BluetoothAddress) = nonceString.toNonce()
|
|
||||||
|
|
||||||
override fun setCurrentTxNonce(pumpAddress: BluetoothAddress, currentTxNonce: Nonce) {
|
|
||||||
nonceString = currentTxNonce.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCurrentUtcOffset(pumpAddress: BluetoothAddress) =
|
|
||||||
UtcOffset(seconds = utcOffsetSeconds)
|
|
||||||
|
|
||||||
override fun setCurrentUtcOffset(pumpAddress: BluetoothAddress, utcOffset: UtcOffset) {
|
|
||||||
utcOffsetSeconds = utcOffset.totalSeconds
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCurrentTbrState(pumpAddress: BluetoothAddress) =
|
|
||||||
if (tbrTimestamp >= 0)
|
|
||||||
CurrentTbrState.TbrStarted(Tbr(
|
|
||||||
timestamp = Instant.fromEpochSeconds(tbrTimestamp),
|
|
||||||
percentage = tbrPercentage,
|
|
||||||
durationInMinutes = tbrDuration,
|
|
||||||
type = Tbr.Type.fromStringId(tbrType)!!
|
|
||||||
))
|
|
||||||
else
|
|
||||||
CurrentTbrState.NoTbrOngoing
|
|
||||||
|
|
||||||
|
|
||||||
override fun setCurrentTbrState(pumpAddress: BluetoothAddress, currentTbrState: CurrentTbrState) {
|
|
||||||
when (currentTbrState) {
|
|
||||||
is CurrentTbrState.TbrStarted -> {
|
|
||||||
tbrTimestamp = currentTbrState.tbr.timestamp.epochSeconds
|
|
||||||
tbrPercentage = currentTbrState.tbr.percentage
|
|
||||||
tbrDuration = currentTbrState.tbr.durationInMinutes
|
|
||||||
tbrType = currentTbrState.tbr.type.stringId
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
tbrTimestamp = -1
|
|
||||||
tbrPercentage = -1
|
|
||||||
tbrDuration = -1
|
|
||||||
tbrType = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val BT_ADDRESS_KEY = "combov2-bt-address-key"
|
|
||||||
const val NONCE_KEY = "combov2-nonce-key"
|
|
||||||
const val CP_CIPHER_KEY = "combov2-cp-cipher-key"
|
|
||||||
const val PC_CIPHER_KEY = "combov2-pc-cipher-key"
|
|
||||||
const val KEY_RESPONSE_ADDRESS_KEY = "combov2-key-response-address-key"
|
|
||||||
const val PUMP_ID_KEY = "combov2-pump-id-key"
|
|
||||||
const val TBR_TIMESTAMP_KEY = "combov2-tbr-timestamp"
|
|
||||||
const val TBR_PERCENTAGE_KEY = "combov2-tbr-percentage"
|
|
||||||
const val TBR_DURATION_KEY = "combov2-tbr-duration"
|
|
||||||
const val TBR_TYPE_KEY = "combov2-tbr-type"
|
|
||||||
const val UTC_OFFSET_KEY = "combov2-utc-offset"
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue