diff --git a/_docs/icons/ic_medtrum_128.svg b/_docs/icons/ic_medtrum_128.svg
new file mode 100644
index 0000000000..98c9b91ac5
--- /dev/null
+++ b/_docs/icons/ic_medtrum_128.svg
@@ -0,0 +1,76 @@
+
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 87da8f35c3..4514dc5e8b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -213,6 +213,7 @@ dependencies {
implementation project(':pump:danar')
implementation project(':pump:diaconn')
implementation project(':pump:eopatch')
+ implementation project(':pump:medtrum')
implementation project(':insight')
implementation project(':pump:medtronic')
implementation project(':pump:pump-common')
diff --git a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt
index 50a4030a9b..7467304b22 100644
--- a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt
@@ -54,6 +54,7 @@ import info.nightscout.plugins.sync.xdrip.XdripPlugin
import info.nightscout.pump.combo.ComboPlugin
import info.nightscout.pump.combov2.ComboV2Plugin
import info.nightscout.pump.diaconn.DiaconnG8Plugin
+import info.nightscout.pump.medtrum.MedtrumPlugin
import info.nightscout.pump.virtual.VirtualPumpPlugin
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventPreferenceChange
@@ -122,6 +123,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
@Inject lateinit var wearPlugin: WearPlugin
@Inject lateinit var maintenancePlugin: MaintenancePlugin
@Inject lateinit var eopatchPumpPlugin: EopatchPumpPlugin
+ @Inject lateinit var medtrumPlugin: MedtrumPlugin
@Inject lateinit var passwordCheck: PasswordCheck
@Inject lateinit var nsSettingStatus: NSSettingsStatus
@@ -212,6 +214,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
addPreferencesFromResourceIfEnabled(medtronicPumpPlugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(diaconnG8Plugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(eopatchPumpPlugin, rootKey, config.PUMPDRIVERS)
+ addPreferencesFromResourceIfEnabled(medtrumPlugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResource(R.xml.pref_pump, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(virtualPumpPlugin, rootKey)
addPreferencesFromResourceIfEnabled(insulinOrefFreePeakPlugin, rootKey)
diff --git a/app/src/main/java/info/nightscout/androidaps/di/AppComponent.kt b/app/src/main/java/info/nightscout/androidaps/di/AppComponent.kt
index def98a723c..f96fd72374 100644
--- a/app/src/main/java/info/nightscout/androidaps/di/AppComponent.kt
+++ b/app/src/main/java/info/nightscout/androidaps/di/AppComponent.kt
@@ -31,6 +31,7 @@ import info.nightscout.pump.common.di.PumpCommonModule
import info.nightscout.pump.dana.di.DanaHistoryModule
import info.nightscout.pump.dana.di.DanaModule
import info.nightscout.pump.danars.di.DanaRSModule
+import info.nightscout.pump.medtrum.di.MedtrumModule
import info.nightscout.pump.diaconn.di.DiaconnG8Module
import info.nightscout.pump.virtual.di.VirtualPumpModule
import info.nightscout.rx.di.RxModule
@@ -87,6 +88,7 @@ import javax.inject.Singleton
OmnipodErosModule::class,
PumpCommonModule::class,
RileyLinkModule::class,
+ MedtrumModule::class,
VirtualPumpModule::class
]
)
diff --git a/app/src/main/java/info/nightscout/androidaps/di/PluginsListModule.kt b/app/src/main/java/info/nightscout/androidaps/di/PluginsListModule.kt
index b30a77bf89..4753c24877 100644
--- a/app/src/main/java/info/nightscout/androidaps/di/PluginsListModule.kt
+++ b/app/src/main/java/info/nightscout/androidaps/di/PluginsListModule.kt
@@ -46,6 +46,7 @@ import info.nightscout.plugins.sync.tidepool.TidepoolPlugin
import info.nightscout.plugins.sync.xdrip.XdripPlugin
import info.nightscout.pump.combo.ComboPlugin
import info.nightscout.pump.combov2.ComboV2Plugin
+import info.nightscout.pump.medtrum.MedtrumPlugin
import info.nightscout.pump.diaconn.DiaconnG8Plugin
import info.nightscout.pump.virtual.VirtualPumpPlugin
import info.nightscout.sensitivity.SensitivityAAPSPlugin
@@ -209,6 +210,12 @@ abstract class PluginsListModule {
@IntKey(156)
abstract fun bindEopatchPumpPlugin(plugin: EopatchPumpPlugin): PluginBase
+ @Binds
+ @PumpDriver
+ @IntoMap
+ @IntKey(160)
+ abstract fun bindMedtrumPlugin(plugin: MedtrumPlugin): PluginBase
+
@Binds
@AllConfigs
@IntoMap
diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/notifications/Notification.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/notifications/Notification.kt
index 066e412c5f..b15459c9b0 100644
--- a/core/interfaces/src/main/java/info/nightscout/interfaces/notifications/Notification.kt
+++ b/core/interfaces/src/main/java/info/nightscout/interfaces/notifications/Notification.kt
@@ -131,9 +131,12 @@ open class Notification {
const val IDENTIFICATION_NOT_SET = 77
const val PERMISSION_BT = 78
const val EOELOW_PATCH_ALERTS = 79
- const val COMBO_PUMP_SUSPENDED = 80
+ const val PUMP_SUSPENDED = 80
const val COMBO_UNKNOWN_TBR = 81
const val BLUETOOTH_NOT_ENABLED = 82
+ const val PATCH_NOT_ACTIVE = 83
+ const val PUMP_SETTINGS_FAILED = 84
+ const val PUMP_TIMEZONE_UPDATE_FAILED = 85
const val USER_MESSAGE = 1000
diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/pump/Medtrum.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/pump/Medtrum.kt
new file mode 100644
index 0000000000..a574e3a609
--- /dev/null
+++ b/core/interfaces/src/main/java/info/nightscout/interfaces/pump/Medtrum.kt
@@ -0,0 +1,13 @@
+package info.nightscout.interfaces.pump
+
+/**
+ * Functionality supported by Medtrum* pumps only
+ */
+interface Medtrum {
+
+ fun loadEvents(): PumpEnactResult // events history to build treatments from
+ fun setUserOptions(): PumpEnactResult // set user settings
+ fun clearAlarms(): PumpEnactResult // clear alarms
+ fun deactivate(): PumpEnactResult // deactivate patch
+ fun updateTime(): PumpEnactResult // update time
+}
diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/pump/defs/ManufacturerType.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/pump/defs/ManufacturerType.kt
index ddecad988c..8ffcfeb6c6 100644
--- a/core/interfaces/src/main/java/info/nightscout/interfaces/pump/defs/ManufacturerType.kt
+++ b/core/interfaces/src/main/java/info/nightscout/interfaces/pump/defs/ManufacturerType.kt
@@ -2,6 +2,7 @@ package info.nightscout.interfaces.pump.defs
enum class ManufacturerType(val description: String) {
AAPS("AAPS"),
+ Medtrum("Medtrum"),
Medtronic("Medtronic"),
Sooil("SOOIL"),
Tandem("Tandem"),
diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/pump/defs/PumpCapability.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/pump/defs/PumpCapability.kt
index ed9620a818..b2557ff80b 100644
--- a/core/interfaces/src/main/java/info/nightscout/interfaces/pump/defs/PumpCapability.kt
+++ b/core/interfaces/src/main/java/info/nightscout/interfaces/pump/defs/PumpCapability.kt
@@ -26,6 +26,7 @@ enum class PumpCapability {
YpsomedCapabilities(arrayOf(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, ReplaceBattery, TDD, ManualTDDLoad)), // BasalRates (separately grouped)
DiaconnCapabilities(arrayOf(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, ReplaceBattery, TDD, ManualTDDLoad)), //
EopatchCapabilities(arrayOf(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, BasalRate30min)),
+ MedtrumCapabilities(arrayOf(Bolus, TempBasal, BasalProfileSet, BasalRate30min, TDD)), // Technically the pump supports ExtendedBolus, but not implemented (yet)
BasalRate_Duration15minAllowed,
BasalRate_Duration30minAllowed,
BasalRate_Duration15and30minAllowed(arrayOf(BasalRate_Duration15minAllowed, BasalRate_Duration30minAllowed)),
diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/pump/defs/PumpType.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/pump/defs/PumpType.kt
index 30163c9ddf..3bd481629c 100644
--- a/core/interfaces/src/main/java/info/nightscout/interfaces/pump/defs/PumpType.kt
+++ b/core/interfaces/src/main/java/info/nightscout/interfaces/pump/defs/PumpType.kt
@@ -391,7 +391,29 @@ enum class PumpType {
isPatchPump = true,
maxReservoirReading = 50,
source = Source.EOPatch2
- );
+ ),
+
+ //Medtrum Nano Pump
+ MEDTRUM_NANO(
+ description = "Medtrum Nano",
+ manufacturer = ManufacturerType.Medtrum,
+ model = "Nano",
+ bolusSize = 0.05,
+ specialBolusSize = null,
+ extendedBolusSettings = DoseSettings(0.05, 30, 8 * 60, 0.05, 30.0),
+ pumpTempBasalType = PumpTempBasalType.Absolute,
+ tbrSettings = DoseSettings(0.05, 30, 12 * 60, 0.0, 25.0),
+ specialBasalDurations = PumpCapability.BasalRate_Duration30minAllowed,
+ baseBasalMinValue = 0.05,
+ baseBasalMaxValue = 25.0,
+ baseBasalStep = 0.05,
+ baseBasalSpecialSteps = null,
+ pumpCapability = PumpCapability.MedtrumCapabilities,
+ isPatchPump = true,
+ maxReservoirReading = 400,
+ source = Source.Medtrum
+ ),
+ MEDTRUM_UNTESTED(description = "Medtrum untested", model = "untested", parent = MEDTRUM_NANO);
val description: String
var manufacturer: ManufacturerType? = null
@@ -458,6 +480,7 @@ enum class PumpType {
OmnipodEros,
OmnipodDash,
EOPatch2,
+ Medtrum,
MDI,
VirtualPump,
Unknown
diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/queue/Command.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/queue/Command.kt
index 977613aa81..545630ecc3 100644
--- a/core/interfaces/src/main/java/info/nightscout/interfaces/queue/Command.kt
+++ b/core/interfaces/src/main/java/info/nightscout/interfaces/queue/Command.kt
@@ -23,11 +23,14 @@ abstract class Command(
BASAL_PROFILE,
READSTATUS,
LOAD_HISTORY, // TDDs and so far only Dana specific
- LOAD_EVENTS, // so far only Dana specific
+ LOAD_EVENTS,
LOAD_TDD,
SET_USER_SETTINGS, // so far only Dana specific,
START_PUMP,
STOP_PUMP,
+ CLEAR_ALARMS, // so far only Medtrum specific
+ DEACTIVATE, // so far only Medtrum specific
+ UPDATE_TIME, // so far only Medtrum specific
INSIGHT_SET_TBR_OVER_ALARM, // insight only
CUSTOM_COMMAND
}
diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/queue/CommandQueue.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/queue/CommandQueue.kt
index eadf4663db..047263c3b2 100644
--- a/core/interfaces/src/main/java/info/nightscout/interfaces/queue/CommandQueue.kt
+++ b/core/interfaces/src/main/java/info/nightscout/interfaces/queue/CommandQueue.kt
@@ -31,6 +31,9 @@ interface CommandQueue {
fun setUserOptions(callback: Callback?): Boolean
fun loadTDDs(callback: Callback?): Boolean
fun loadEvents(callback: Callback?): Boolean
+ fun clearAlarms(callback: Callback?): Boolean
+ fun deactivate(callback: Callback?): Boolean
+ fun updateTime(callback: Callback?): Boolean
fun customCommand(customCommand: CustomCommand, callback: Callback?): Boolean
fun isCustomCommandRunning(customCommandType: Class): Boolean
fun isCustomCommandInQueue(customCommandType: Class): Boolean
diff --git a/core/main/src/main/java/info/nightscout/core/pump/PumpTypeExtension.kt b/core/main/src/main/java/info/nightscout/core/pump/PumpTypeExtension.kt
index 7024036f9e..321365cba4 100644
--- a/core/main/src/main/java/info/nightscout/core/pump/PumpTypeExtension.kt
+++ b/core/main/src/main/java/info/nightscout/core/pump/PumpTypeExtension.kt
@@ -59,6 +59,8 @@ fun PumpType.Companion.fromDbPumpType(pt: InterfaceIDs.PumpType): PumpType =
InterfaceIDs.PumpType.USER -> PumpType.USER
InterfaceIDs.PumpType.DIACONN_G8 -> PumpType.DIACONN_G8
InterfaceIDs.PumpType.EOPATCH2 -> PumpType.EOFLOW_EOPATCH2
+ InterfaceIDs.PumpType.MEDTRUM -> PumpType.MEDTRUM_NANO
+ InterfaceIDs.PumpType.MEDTRUM_UNTESTED -> PumpType.MEDTRUM_UNTESTED
InterfaceIDs.PumpType.CACHE -> PumpType.CACHE
}
@@ -117,5 +119,7 @@ fun PumpType.toDbPumpType(): InterfaceIDs.PumpType =
PumpType.USER -> InterfaceIDs.PumpType.USER
PumpType.DIACONN_G8 -> InterfaceIDs.PumpType.DIACONN_G8
PumpType.EOFLOW_EOPATCH2 -> InterfaceIDs.PumpType.EOPATCH2
+ PumpType.MEDTRUM_NANO -> InterfaceIDs.PumpType.MEDTRUM
+ PumpType.MEDTRUM_UNTESTED -> InterfaceIDs.PumpType.MEDTRUM_UNTESTED
PumpType.CACHE -> InterfaceIDs.PumpType.CACHE
}
diff --git a/core/ui/src/main/res/drawable/ic_medtrum_128.xml b/core/ui/src/main/res/drawable/ic_medtrum_128.xml
new file mode 100644
index 0000000000..a8e35cd3e0
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_medtrum_128.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml
index 075ac674bf..96e341ef5d 100644
--- a/core/ui/src/main/res/values/strings.xml
+++ b/core/ui/src/main/res/values/strings.xml
@@ -393,6 +393,9 @@
CARBS %1$d g
EXTENDED BOLUS %1$.2f U %2$d min
LOAD EVENTS
+ CLEAR ALARMS
+ DEACTIVATE
+ UPDATE TIME
LOAD HISTORY %1$d
LOAD TDDs
SET PROFILE
diff --git a/database/entities/src/main/java/info/nightscout/database/entities/embedments/InterfaceIDs.kt b/database/entities/src/main/java/info/nightscout/database/entities/embedments/InterfaceIDs.kt
index 5f78da4e8c..0a244aa3c6 100644
--- a/database/entities/src/main/java/info/nightscout/database/entities/embedments/InterfaceIDs.kt
+++ b/database/entities/src/main/java/info/nightscout/database/entities/embedments/InterfaceIDs.kt
@@ -43,6 +43,8 @@ data class InterfaceIDs(
MDI,
DIACONN_G8,
EOPATCH2,
+ MEDTRUM,
+ MEDTRUM_UNTESTED,
USER,
CACHE;
diff --git a/implementation/src/main/java/info/nightscout/implementation/di/CommandQueueModule.kt b/implementation/src/main/java/info/nightscout/implementation/di/CommandQueueModule.kt
index e6864c9508..3c83defd4f 100644
--- a/implementation/src/main/java/info/nightscout/implementation/di/CommandQueueModule.kt
+++ b/implementation/src/main/java/info/nightscout/implementation/di/CommandQueueModule.kt
@@ -14,12 +14,15 @@ import info.nightscout.implementation.queue.commands.CommandTempBasalPercent
import info.nightscout.implementation.queue.commands.CommandBolus
import info.nightscout.implementation.queue.commands.CommandCancelExtendedBolus
import info.nightscout.implementation.queue.commands.CommandCancelTempBasal
+import info.nightscout.implementation.queue.commands.CommandClearAlarms
import info.nightscout.implementation.queue.commands.CommandCustomCommand
+import info.nightscout.implementation.queue.commands.CommandDeactivate
import info.nightscout.implementation.queue.commands.CommandExtendedBolus
import info.nightscout.implementation.queue.commands.CommandInsightSetTBROverNotification
import info.nightscout.implementation.queue.commands.CommandLoadEvents
import info.nightscout.implementation.queue.commands.CommandLoadHistory
import info.nightscout.implementation.queue.commands.CommandLoadTDDs
+import info.nightscout.implementation.queue.commands.CommandUpdateTime
@Module
@Suppress("unused")
@@ -32,6 +35,9 @@ abstract class CommandQueueModule {
@ContributesAndroidInjector abstract fun commandExtendedBolusInjector(): CommandExtendedBolus
@ContributesAndroidInjector abstract fun commandInsightSetTBROverNotificationInjector(): CommandInsightSetTBROverNotification
@ContributesAndroidInjector abstract fun commandLoadEventsInjector(): CommandLoadEvents
+ @ContributesAndroidInjector abstract fun commandClearAlarmsInjector(): CommandClearAlarms
+ @ContributesAndroidInjector abstract fun commandDeactivateInjector(): CommandDeactivate
+ @ContributesAndroidInjector abstract fun commandUpdateTimeInjector(): CommandUpdateTime
@ContributesAndroidInjector abstract fun commandLoadHistoryInjector(): CommandLoadHistory
@ContributesAndroidInjector abstract fun commandLoadTDDsInjector(): CommandLoadTDDs
@ContributesAndroidInjector abstract fun commandReadStatusInjector(): CommandReadStatus
diff --git a/implementation/src/main/java/info/nightscout/implementation/pump/DetailedBolusInfoStorageImpl.kt b/implementation/src/main/java/info/nightscout/implementation/pump/DetailedBolusInfoStorageImpl.kt
index 24628ece59..d476796218 100644
--- a/implementation/src/main/java/info/nightscout/implementation/pump/DetailedBolusInfoStorageImpl.kt
+++ b/implementation/src/main/java/info/nightscout/implementation/pump/DetailedBolusInfoStorageImpl.kt
@@ -1,11 +1,15 @@
package info.nightscout.implementation.pump
import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
import info.nightscout.androidaps.annotations.OpenForTesting
+import info.nightscout.implementation.R
import info.nightscout.interfaces.pump.DetailedBolusInfo
import info.nightscout.interfaces.pump.DetailedBolusInfoStorage
import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.utils.T
import javax.inject.Inject
import javax.inject.Singleton
@@ -14,10 +18,12 @@ import kotlin.math.abs
@OpenForTesting
@Singleton
class DetailedBolusInfoStorageImpl @Inject constructor(
- val aapsLogger: AAPSLogger
+ val aapsLogger: AAPSLogger,
+ val sp: SP,
+ val rh: ResourceHelper
) : DetailedBolusInfoStorage {
- val store = ArrayList()
+ val store = loadStore()
fun DetailedBolusInfo.toJsonString(): String = Gson().toJson(this)
@@ -25,6 +31,7 @@ class DetailedBolusInfoStorageImpl @Inject constructor(
override fun add(detailedBolusInfo: DetailedBolusInfo) {
aapsLogger.debug("Stored bolus info: ${detailedBolusInfo.toJsonString()}")
store.add(detailedBolusInfo)
+ saveStore()
}
@Synchronized
@@ -36,6 +43,7 @@ class DetailedBolusInfoStorageImpl @Inject constructor(
if (bolusTime > d.timestamp - T.mins(1).msecs() && bolusTime < d.timestamp + T.mins(1).msecs() && abs(store[i].insulin - bolus) < 0.01) {
aapsLogger.debug(LTag.PUMP, "Using & removing bolus info for time $bolusTime: ${store[i]}")
store.removeAt(i)
+ saveStore()
return d
}
}
@@ -45,6 +53,7 @@ class DetailedBolusInfoStorageImpl @Inject constructor(
if (bolusTime > d.timestamp - T.mins(1).msecs() && bolusTime < d.timestamp + T.mins(1).msecs() && bolus <= store[i].insulin + 0.01) {
aapsLogger.debug(LTag.PUMP, "Using TIME-ONLY & removing bolus info for time $bolusTime: ${store[i]}")
store.removeAt(i)
+ saveStore()
return d
}
}
@@ -61,4 +70,24 @@ class DetailedBolusInfoStorageImpl @Inject constructor(
aapsLogger.debug(LTag.PUMP, "Bolus info not found for time $bolusTime")
return null
}
+
+ private fun saveStore() {
+ var lastTwoEntries = store
+ // Only save last two entries, to avoid too much data in preferences
+ if (store.size > 2) {
+ lastTwoEntries = ArrayList(store.subList(store.size - 2, store.size))
+ }
+ val jsonString = Gson().toJson(lastTwoEntries)
+ sp.putString(rh.gs(R.string.key_bolus_storage), jsonString)
+ }
+
+ private fun loadStore(): ArrayList {
+ val jsonString = sp.getString(rh.gs(R.string.key_bolus_storage), "")
+ return if (jsonString != null && jsonString.isNotEmpty()) {
+ val type = object : TypeToken>() {}.type
+ Gson().fromJson(jsonString, type)
+ } else {
+ ArrayList()
+ }
+ }
}
\ No newline at end of file
diff --git a/implementation/src/main/java/info/nightscout/implementation/queue/CommandQueueImplementation.kt b/implementation/src/main/java/info/nightscout/implementation/queue/CommandQueueImplementation.kt
index 894d7aa308..63ebd67901 100644
--- a/implementation/src/main/java/info/nightscout/implementation/queue/CommandQueueImplementation.kt
+++ b/implementation/src/main/java/info/nightscout/implementation/queue/CommandQueueImplementation.kt
@@ -22,7 +22,9 @@ import info.nightscout.implementation.R
import info.nightscout.implementation.queue.commands.CommandBolus
import info.nightscout.implementation.queue.commands.CommandCancelExtendedBolus
import info.nightscout.implementation.queue.commands.CommandCancelTempBasal
+import info.nightscout.implementation.queue.commands.CommandClearAlarms
import info.nightscout.implementation.queue.commands.CommandCustomCommand
+import info.nightscout.implementation.queue.commands.CommandDeactivate
import info.nightscout.implementation.queue.commands.CommandExtendedBolus
import info.nightscout.implementation.queue.commands.CommandInsightSetTBROverNotification
import info.nightscout.implementation.queue.commands.CommandLoadEvents
@@ -36,6 +38,7 @@ import info.nightscout.implementation.queue.commands.CommandStartPump
import info.nightscout.implementation.queue.commands.CommandStopPump
import info.nightscout.implementation.queue.commands.CommandTempBasalAbsolute
import info.nightscout.implementation.queue.commands.CommandTempBasalPercent
+import info.nightscout.implementation.queue.commands.CommandUpdateTime
import info.nightscout.interfaces.AndroidPermission
import info.nightscout.interfaces.Config
import info.nightscout.interfaces.constraints.Constraint
@@ -535,6 +538,46 @@ class CommandQueueImplementation @Inject constructor(
return true
}
+ // returns true if command is queued
+ override fun clearAlarms(callback: Callback?): Boolean {
+ if (isRunning(CommandType.CLEAR_ALARMS)) {
+ callback?.result(executingNowError())?.run()
+ return false
+ }
+ // remove all unfinished
+ removeAll(CommandType.CLEAR_ALARMS)
+ // add new command to queue
+ add(CommandClearAlarms(injector, callback))
+ notifyAboutNewCommand()
+ return true
+ }
+
+ override fun deactivate(callback: Callback?): Boolean {
+ if (isRunning(CommandType.DEACTIVATE)) {
+ callback?.result(executingNowError())?.run()
+ return false
+ }
+ // remove all unfinished
+ removeAll(CommandType.DEACTIVATE)
+ // add new command to queue
+ add(CommandDeactivate(injector, callback))
+ notifyAboutNewCommand()
+ return true
+ }
+
+ override fun updateTime(callback: Callback?): Boolean {
+ if (isRunning(CommandType.UPDATE_TIME)) {
+ callback?.result(executingNowError())?.run()
+ return false
+ }
+ // remove all unfinished
+ removeAll(CommandType.UPDATE_TIME)
+ // add new command to queue
+ add(CommandUpdateTime(injector, callback))
+ notifyAboutNewCommand()
+ return true
+ }
+
override fun customCommand(customCommand: CustomCommand, callback: Callback?): Boolean {
if (isCustomCommandInQueue(customCommand.javaClass)) {
callback?.result(executingNowError())?.run()
diff --git a/implementation/src/main/java/info/nightscout/implementation/queue/commands/CommandClearAlarms.kt b/implementation/src/main/java/info/nightscout/implementation/queue/commands/CommandClearAlarms.kt
new file mode 100644
index 0000000000..00e5bb1ff0
--- /dev/null
+++ b/implementation/src/main/java/info/nightscout/implementation/queue/commands/CommandClearAlarms.kt
@@ -0,0 +1,39 @@
+package info.nightscout.implementation.queue.commands
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.interfaces.plugin.ActivePlugin
+import info.nightscout.interfaces.pump.Dana
+import info.nightscout.interfaces.pump.Diaconn
+import info.nightscout.interfaces.pump.Medtrum
+import info.nightscout.interfaces.pump.PumpEnactResult
+import info.nightscout.interfaces.queue.Callback
+import info.nightscout.interfaces.queue.Command
+import info.nightscout.rx.logging.LTag
+import javax.inject.Inject
+
+class CommandClearAlarms(
+ injector: HasAndroidInjector,
+ callback: Callback?
+) : Command(injector, CommandType.CLEAR_ALARMS, callback) {
+
+ @Inject lateinit var activePlugin: ActivePlugin
+
+ override fun execute() {
+ val pump = activePlugin.activePump
+
+ if (pump is Medtrum) {
+ val medtrumPump = pump as Medtrum
+ val r = medtrumPump.clearAlarms()
+ aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
+ callback?.result(r)?.run()
+ }
+ }
+
+ override fun status(): String = rh.gs(info.nightscout.core.ui.R.string.clear_alarms)
+
+ override fun log(): String = "CLEAR ALARMS"
+ override fun cancel() {
+ aapsLogger.debug(LTag.PUMPQUEUE, "Result cancel")
+ callback?.result(PumpEnactResult(injector).success(false).comment(info.nightscout.core.ui.R.string.connectiontimedout))?.run()
+ }
+}
diff --git a/implementation/src/main/java/info/nightscout/implementation/queue/commands/CommandDeactivate.kt b/implementation/src/main/java/info/nightscout/implementation/queue/commands/CommandDeactivate.kt
new file mode 100644
index 0000000000..eef55c1ecc
--- /dev/null
+++ b/implementation/src/main/java/info/nightscout/implementation/queue/commands/CommandDeactivate.kt
@@ -0,0 +1,39 @@
+package info.nightscout.implementation.queue.commands
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.interfaces.plugin.ActivePlugin
+import info.nightscout.interfaces.pump.Dana
+import info.nightscout.interfaces.pump.Diaconn
+import info.nightscout.interfaces.pump.Medtrum
+import info.nightscout.interfaces.pump.PumpEnactResult
+import info.nightscout.interfaces.queue.Callback
+import info.nightscout.interfaces.queue.Command
+import info.nightscout.rx.logging.LTag
+import javax.inject.Inject
+
+class CommandDeactivate(
+ injector: HasAndroidInjector,
+ callback: Callback?
+) : Command(injector, CommandType.DEACTIVATE, callback) {
+
+ @Inject lateinit var activePlugin: ActivePlugin
+
+ override fun execute() {
+ val pump = activePlugin.activePump
+
+ if (pump is Medtrum) {
+ val medtrumPump = pump as Medtrum
+ val r = medtrumPump.deactivate()
+ aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
+ callback?.result(r)?.run()
+ }
+ }
+
+ override fun status(): String = rh.gs(info.nightscout.core.ui.R.string.deactivate)
+
+ override fun log(): String = "DEACTIVATE"
+ override fun cancel() {
+ aapsLogger.debug(LTag.PUMPQUEUE, "Result cancel")
+ callback?.result(PumpEnactResult(injector).success(false).comment(info.nightscout.core.ui.R.string.connectiontimedout))?.run()
+ }
+}
diff --git a/implementation/src/main/java/info/nightscout/implementation/queue/commands/CommandLoadEvents.kt b/implementation/src/main/java/info/nightscout/implementation/queue/commands/CommandLoadEvents.kt
index df6fb48cef..16475adf4f 100644
--- a/implementation/src/main/java/info/nightscout/implementation/queue/commands/CommandLoadEvents.kt
+++ b/implementation/src/main/java/info/nightscout/implementation/queue/commands/CommandLoadEvents.kt
@@ -4,6 +4,7 @@ import dagger.android.HasAndroidInjector
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.pump.Dana
import info.nightscout.interfaces.pump.Diaconn
+import info.nightscout.interfaces.pump.Medtrum
import info.nightscout.interfaces.pump.PumpEnactResult
import info.nightscout.interfaces.queue.Callback
import info.nightscout.interfaces.queue.Command
@@ -32,6 +33,13 @@ class CommandLoadEvents(
aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
callback?.result(r)?.run()
}
+
+ if (pump is Medtrum) {
+ val medtrumPump = pump as Medtrum
+ val r = medtrumPump.loadEvents()
+ aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
+ callback?.result(r)?.run()
+ }
}
override fun status(): String = rh.gs(info.nightscout.core.ui.R.string.load_events)
diff --git a/implementation/src/main/java/info/nightscout/implementation/queue/commands/CommandSetUserSettings.kt b/implementation/src/main/java/info/nightscout/implementation/queue/commands/CommandSetUserSettings.kt
index 471c83c670..cb120cf32a 100644
--- a/implementation/src/main/java/info/nightscout/implementation/queue/commands/CommandSetUserSettings.kt
+++ b/implementation/src/main/java/info/nightscout/implementation/queue/commands/CommandSetUserSettings.kt
@@ -4,6 +4,7 @@ import dagger.android.HasAndroidInjector
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.pump.Dana
import info.nightscout.interfaces.pump.Diaconn
+import info.nightscout.interfaces.pump.Medtrum
import info.nightscout.interfaces.pump.PumpEnactResult
import info.nightscout.interfaces.queue.Callback
import info.nightscout.interfaces.queue.Command
@@ -30,6 +31,12 @@ class CommandSetUserSettings(
aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
callback?.result(r)?.run()
}
+
+ if (pump is Medtrum) {
+ val r = pump.setUserOptions()
+ aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
+ callback?.result(r)?.run()
+ }
}
override fun status(): String = rh.gs(info.nightscout.core.ui.R.string.set_user_settings)
diff --git a/implementation/src/main/java/info/nightscout/implementation/queue/commands/CommandUpdateTime.kt b/implementation/src/main/java/info/nightscout/implementation/queue/commands/CommandUpdateTime.kt
new file mode 100644
index 0000000000..a8b5c5654b
--- /dev/null
+++ b/implementation/src/main/java/info/nightscout/implementation/queue/commands/CommandUpdateTime.kt
@@ -0,0 +1,39 @@
+package info.nightscout.implementation.queue.commands
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.interfaces.plugin.ActivePlugin
+import info.nightscout.interfaces.pump.Dana
+import info.nightscout.interfaces.pump.Diaconn
+import info.nightscout.interfaces.pump.Medtrum
+import info.nightscout.interfaces.pump.PumpEnactResult
+import info.nightscout.interfaces.queue.Callback
+import info.nightscout.interfaces.queue.Command
+import info.nightscout.rx.logging.LTag
+import javax.inject.Inject
+
+class CommandUpdateTime(
+ injector: HasAndroidInjector,
+ callback: Callback?
+) : Command(injector, CommandType.UPDATE_TIME, callback) {
+
+ @Inject lateinit var activePlugin: ActivePlugin
+
+ override fun execute() {
+ val pump = activePlugin.activePump
+
+ if (pump is Medtrum) {
+ val medtrumPump = pump as Medtrum
+ val r = medtrumPump.updateTime()
+ aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
+ callback?.result(r)?.run()
+ }
+ }
+
+ override fun status(): String = rh.gs(info.nightscout.core.ui.R.string.update_time)
+
+ override fun log(): String = "UPDATE TIME"
+ override fun cancel() {
+ aapsLogger.debug(LTag.PUMPQUEUE, "Result cancel")
+ callback?.result(PumpEnactResult(injector).success(false).comment(info.nightscout.core.ui.R.string.connectiontimedout))?.run()
+ }
+}
diff --git a/implementation/src/main/res/values/strings.xml b/implementation/src/main/res/values/strings.xml
index c9d2c3b0ab..2cbfb9810f 100644
--- a/implementation/src/main/res/values/strings.xml
+++ b/implementation/src/main/res/values/strings.xml
@@ -1,5 +1,6 @@
+ key_bolus_storage
BG
Command is executed right now
diff --git a/implementation/src/test/java/info/nightscout/implementation/pump/DetailedBolusInfoStorageTest.kt b/implementation/src/test/java/info/nightscout/implementation/pump/DetailedBolusInfoStorageTest.kt
index 3831f62381..91d38733d8 100644
--- a/implementation/src/test/java/info/nightscout/implementation/pump/DetailedBolusInfoStorageTest.kt
+++ b/implementation/src/test/java/info/nightscout/implementation/pump/DetailedBolusInfoStorageTest.kt
@@ -2,13 +2,19 @@ package info.nightscout.implementation.pump
import info.nightscout.androidaps.TestBase
import info.nightscout.interfaces.pump.DetailedBolusInfo
+import info.nightscout.shared.interfaces.ResourceHelper
+import info.nightscout.shared.sharedPreferences.SP
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
+import org.mockito.Mock
class DetailedBolusInfoStorageTest : TestBase() {
+ @Mock lateinit var sp: SP
+ @Mock lateinit var rh: ResourceHelper
+
private val info1 = DetailedBolusInfo()
private val info2 = DetailedBolusInfo()
private val info3 = DetailedBolusInfo()
@@ -26,7 +32,7 @@ class DetailedBolusInfoStorageTest : TestBase() {
@BeforeEach
fun prepare() {
- detailedBolusInfoStorage = DetailedBolusInfoStorageImpl(aapsLogger)
+ detailedBolusInfoStorage = DetailedBolusInfoStorageImpl(aapsLogger, sp, rh)
}
private fun setUp() {
diff --git a/implementation/src/test/java/info/nightscout/implementation/queue/CommandQueueImplementationTest.kt b/implementation/src/test/java/info/nightscout/implementation/queue/CommandQueueImplementationTest.kt
index aaaa6657ce..75183844ab 100644
--- a/implementation/src/test/java/info/nightscout/implementation/queue/CommandQueueImplementationTest.kt
+++ b/implementation/src/test/java/info/nightscout/implementation/queue/CommandQueueImplementationTest.kt
@@ -238,6 +238,19 @@ class CommandQueueImplementationTest : TestBaseWithProfile() {
// add loadEvents
commandQueue.loadEvents(null)
Assertions.assertEquals(4, commandQueue.size())
+
+ // add clearAlarms
+ commandQueue.clearAlarms(null)
+ Assertions.assertEquals(5, commandQueue.size())
+
+ // add deactivate
+ commandQueue.deactivate(null)
+ Assertions.assertEquals(6, commandQueue.size())
+
+ // add updateTime
+ commandQueue.updateTime(null)
+ Assertions.assertEquals(7, commandQueue.size())
+
commandQueue.clear()
commandQueue.tempBasalAbsolute(0.0, 30, true, validProfile, PumpSync.TemporaryBasalType.NORMAL, null)
commandQueue.pickup()
@@ -354,6 +367,54 @@ class CommandQueueImplementationTest : TestBaseWithProfile() {
Assertions.assertEquals(1, commandQueue.size())
}
+ @Test
+ fun isClearAlarmsCommandInQueue() {
+ // given
+ Assertions.assertEquals(0, commandQueue.size())
+
+ // when
+ commandQueue.clearAlarms(null)
+
+ // then
+ Assertions.assertTrue(commandQueue.isLastScheduled(Command.CommandType.CLEAR_ALARMS))
+ Assertions.assertEquals(1, commandQueue.size())
+ // next should be ignored
+ commandQueue.clearAlarms(null)
+ Assertions.assertEquals(1, commandQueue.size())
+ }
+
+ @Test
+ fun isDeactivateCommandInQueue() {
+ // given
+ Assertions.assertEquals(0, commandQueue.size())
+
+ // when
+ commandQueue.deactivate(null)
+
+ // then
+ Assertions.assertTrue(commandQueue.isLastScheduled(Command.CommandType.DEACTIVATE))
+ Assertions.assertEquals(1, commandQueue.size())
+ // next should be ignored
+ commandQueue.deactivate(null)
+ Assertions.assertEquals(1, commandQueue.size())
+ }
+
+ @Test
+ fun isUpdateTimeCommandInQueue() {
+ // given
+ Assertions.assertEquals(0, commandQueue.size())
+
+ // when
+ commandQueue.updateTime(null)
+
+ // then
+ Assertions.assertTrue(commandQueue.isLastScheduled(Command.CommandType.UPDATE_TIME))
+ Assertions.assertEquals(1, commandQueue.size())
+ // next should be ignored
+ commandQueue.updateTime(null)
+ Assertions.assertEquals(1, commandQueue.size())
+ }
+
@Test
fun isLoadTDDsCommandInQueue() {
// given
diff --git a/plugins/configuration/src/main/java/info/nightscout/configuration/setupwizard/SWDefinition.kt b/plugins/configuration/src/main/java/info/nightscout/configuration/setupwizard/SWDefinition.kt
index 27465d04f4..ec5858f591 100644
--- a/plugins/configuration/src/main/java/info/nightscout/configuration/setupwizard/SWDefinition.kt
+++ b/plugins/configuration/src/main/java/info/nightscout/configuration/setupwizard/SWDefinition.kt
@@ -35,6 +35,7 @@ import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginType
import info.nightscout.interfaces.profile.ProfileFunction
+import info.nightscout.interfaces.pump.Medtrum
import info.nightscout.interfaces.pump.OmnipodDash
import info.nightscout.interfaces.pump.OmnipodEros
import info.nightscout.interfaces.queue.CommandQueue
@@ -319,23 +320,24 @@ class SWDefinition @Inject constructor(
.text(R.string.readstatus)
.action { commandQueue.readStatus(rh.gs(info.nightscout.core.ui.R.string.clicked_connect_to_pump), null) }
.visibility {
- // Hide for Omnipod, because as we don't require a Pod to be paired in the setup wizard,
+ // Hide for Omnipod and Medtrum, because as we don't require a Pod/Patch to be paired in the setup wizard,
// Getting the status might not be possible
- activePlugin.activePump !is OmnipodEros && activePlugin.activePump !is OmnipodDash
+ activePlugin.activePump !is OmnipodEros && activePlugin.activePump !is OmnipodDash && activePlugin.activePump !is Medtrum
})
.add(SWEventListener(injector, EventPumpStatusChanged::class.java)
- .visibility { activePlugin.activePump !is OmnipodEros && activePlugin.activePump !is OmnipodDash })
+ .visibility { activePlugin.activePump !is OmnipodEros && activePlugin.activePump !is OmnipodDash && activePlugin.activePump !is Medtrum })
.validator { isPumpInitialized() }
private fun isPumpInitialized(): Boolean {
val activePump = activePlugin.activePump
- // For Omnipod, activating a Pod can be done after setup through the Omnipod fragment
- // For the Eros model, consider the pump initialized when a RL has been configured successfully
- // For Dash model, consider the pump setup without any extra conditions
+ // For Omnipod and Medtrum, activating a Pod/Patch can be done after setup through the pump fragment
+ // For the Eros, consider the pump initialized when a RL has been configured successfully
+ // For all others, consider the pump setup without any extra conditions
return activePump.isInitialized()
|| (activePump is OmnipodEros && activePump.isRileyLinkReady())
|| activePump is OmnipodDash
+ || activePump is Medtrum
}
private val screenAps
diff --git a/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/ComboV2Plugin.kt b/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/ComboV2Plugin.kt
index 7a095ea7da..cbe56f27f5 100644
--- a/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/ComboV2Plugin.kt
+++ b/pump/combov2/src/main/kotlin/info/nightscout/pump/combov2/ComboV2Plugin.kt
@@ -2226,7 +2226,7 @@ class ComboV2Plugin @Inject constructor (
// only shows up in the Combo fragment.
if (newState == DriverState.Suspended) {
uiInteraction.addNotification(
- Notification.COMBO_PUMP_SUSPENDED,
+ Notification.PUMP_SUSPENDED,
text = rh.gs(R.string.combov2_pump_is_suspended),
level = Notification.NORMAL
)
diff --git a/pump/medtrum/.gitignore b/pump/medtrum/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/pump/medtrum/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/pump/medtrum/build.gradle b/pump/medtrum/build.gradle
new file mode 100644
index 0000000000..2f7325883b
--- /dev/null
+++ b/pump/medtrum/build.gradle
@@ -0,0 +1,33 @@
+plugins {
+ id 'com.android.library'
+ id 'kotlin-android'
+ id 'kotlin-kapt'
+ id 'kotlin-allopen'
+}
+
+apply from: "${project.rootDir}/core/main/android_dependencies.gradle"
+apply from: "${project.rootDir}/core/main/android_module_dependencies.gradle"
+apply from: "${project.rootDir}/core/main/allopen_dependencies.gradle"
+apply from: "${project.rootDir}/core/main/test_dependencies.gradle"
+apply from: "${project.rootDir}/core/main/jacoco_global.gradle"
+
+android {
+ namespace 'info.nightscout.pump.medtrum'
+ dataBinding {
+ enabled = true
+ }
+}
+
+dependencies {
+ implementation project(':app-wear-shared:shared')
+ implementation project(':database:entities')
+ implementation project(':core:libraries')
+ implementation project(':core:interfaces')
+ implementation project(':core:main')
+ implementation project(':core:ui')
+ implementation project(':core:validators')
+ implementation project(':pump:pump-common')
+ implementation project(':core:utils')
+
+ testImplementation project(':core:main')
+}
diff --git a/pump/medtrum/consumer-rules.pro b/pump/medtrum/consumer-rules.pro
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/pump/medtrum/proguard-rules.pro b/pump/medtrum/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/pump/medtrum/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/pump/medtrum/src/main/AndroidManifest.xml b/pump/medtrum/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..22b7e1ba50
--- /dev/null
+++ b/pump/medtrum/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPlugin.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPlugin.kt
new file mode 100644
index 0000000000..ad5e49cbc8
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPlugin.kt
@@ -0,0 +1,472 @@
+package info.nightscout.pump.medtrum
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
+import android.text.format.DateFormat
+import androidx.preference.EditTextPreference
+import androidx.preference.PreferenceFragmentCompat
+import dagger.android.HasAndroidInjector
+import info.nightscout.core.ui.toast.ToastUtils
+import info.nightscout.core.utils.fabric.FabricPrivacy
+import info.nightscout.interfaces.constraints.Constraint
+import info.nightscout.interfaces.constraints.Constraints
+import info.nightscout.interfaces.notifications.Notification
+import info.nightscout.interfaces.plugin.PluginDescription
+import info.nightscout.interfaces.plugin.PluginType
+import info.nightscout.interfaces.profile.Profile
+import info.nightscout.interfaces.pump.DetailedBolusInfo
+import info.nightscout.interfaces.pump.Medtrum
+import info.nightscout.interfaces.pump.Pump
+import info.nightscout.interfaces.pump.PumpEnactResult
+import info.nightscout.interfaces.pump.PumpPluginBase
+import info.nightscout.interfaces.pump.PumpSync
+import info.nightscout.interfaces.pump.TemporaryBasalStorage
+import info.nightscout.interfaces.pump.actions.CustomAction
+import info.nightscout.interfaces.pump.actions.CustomActionType
+import info.nightscout.interfaces.pump.defs.ManufacturerType
+import info.nightscout.interfaces.pump.defs.PumpDescription
+import info.nightscout.interfaces.pump.defs.PumpType
+import info.nightscout.interfaces.queue.Callback
+import info.nightscout.interfaces.queue.CommandQueue
+import info.nightscout.interfaces.queue.CustomCommand
+import info.nightscout.interfaces.ui.UiInteraction
+import info.nightscout.interfaces.utils.DecimalFormatter
+import info.nightscout.interfaces.utils.TimeChangeType
+import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
+import info.nightscout.pump.medtrum.ui.MedtrumOverviewFragment
+import info.nightscout.pump.medtrum.services.MedtrumService
+import info.nightscout.rx.AapsSchedulers
+import info.nightscout.rx.bus.RxBus
+import info.nightscout.rx.events.EventAppExit
+import info.nightscout.rx.events.EventDismissNotification
+import info.nightscout.rx.events.EventOverviewBolusProgress
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.interfaces.ResourceHelper
+import info.nightscout.shared.utils.DateUtil
+import info.nightscout.shared.utils.T
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
+import org.json.JSONException
+import org.json.JSONObject
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlin.math.abs
+
+@Singleton class MedtrumPlugin @Inject constructor(
+ injector: HasAndroidInjector,
+ aapsLogger: AAPSLogger,
+ rh: ResourceHelper,
+ commandQueue: CommandQueue,
+ private val constraintChecker: Constraints,
+ private val aapsSchedulers: AapsSchedulers,
+ private val rxBus: RxBus,
+ private val context: Context,
+ private val fabricPrivacy: FabricPrivacy,
+ private val dateUtil: DateUtil,
+ private val medtrumPump: MedtrumPump,
+ private val uiInteraction: UiInteraction,
+ private val pumpSync: PumpSync,
+ private val temporaryBasalStorage: TemporaryBasalStorage
+) : PumpPluginBase(
+ PluginDescription()
+ .mainType(PluginType.PUMP)
+ .fragmentClass(MedtrumOverviewFragment::class.java.name)
+ .pluginIcon(info.nightscout.core.ui.R.drawable.ic_medtrum_128)
+ .pluginName(R.string.medtrum)
+ .shortName(R.string.medtrum_pump_shortname)
+ .preferencesId(R.xml.pref_medtrum_pump)
+ .description(R.string.medtrum_pump_description), injector, aapsLogger, rh, commandQueue
+), Pump, Medtrum {
+
+ private val disposable = CompositeDisposable()
+ private var medtrumService: MedtrumService? = null
+
+ override fun onStart() {
+ super.onStart()
+ aapsLogger.debug(LTag.PUMP, "MedtrumPlugin onStart()")
+ val intent = Intent(context, MedtrumService::class.java)
+ context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
+ disposable += rxBus
+ .toObservable(EventAppExit::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ context.unbindService(mConnection) }, fabricPrivacy::logException)
+ }
+
+ override fun onStop() {
+ aapsLogger.debug(LTag.PUMP, "MedtrumPlugin onStop()")
+ context.unbindService(mConnection)
+ disposable.clear()
+ super.onStop()
+ }
+
+ private val mConnection: ServiceConnection = object : ServiceConnection {
+ override fun onServiceDisconnected(name: ComponentName) {
+ aapsLogger.debug(LTag.PUMP, "Service is disconnected")
+ medtrumService = null
+ }
+
+ override fun onServiceConnected(name: ComponentName, service: IBinder) {
+ aapsLogger.debug(LTag.PUMP, "Service is connected")
+ val mLocalBinder = service as MedtrumService.LocalBinder
+ medtrumService = mLocalBinder.serviceInstance
+ }
+ }
+
+ fun getService(): MedtrumService? {
+ return medtrumService
+ }
+
+ override fun preprocessPreferences(preferenceFragment: PreferenceFragmentCompat) {
+ super.preprocessPreferences(preferenceFragment)
+ val serialSetting = preferenceFragment.findPreference(rh.gs(R.string.key_sn_input)) as EditTextPreference?
+ serialSetting?.isEnabled = !isInitialized()
+ }
+
+ override fun isInitialized(): Boolean {
+ return medtrumPump.pumpState > MedtrumPumpState.EJECTED && medtrumPump.pumpState < MedtrumPumpState.STOPPED
+ }
+
+ override fun isSuspended(): Boolean {
+ return medtrumPump.pumpState < MedtrumPumpState.ACTIVE || medtrumPump.pumpState > MedtrumPumpState.ACTIVE_ALT
+ }
+
+ override fun isBusy(): Boolean {
+ return false
+ }
+
+ override fun isConnected(): Boolean {
+ // This is a workaround to prevent AAPS to trigger connects when we have no patch activated
+ return if (!isInitialized()) true else medtrumService?.isConnected ?: false
+ }
+
+ override fun isConnecting(): Boolean = medtrumService?.isConnecting ?: false
+ override fun isHandshakeInProgress(): Boolean = false
+
+ override fun finishHandshaking() {
+ }
+
+ override fun connect(reason: String) {
+ if (isInitialized()) {
+ aapsLogger.debug(LTag.PUMP, "Medtrum connect - reason:$reason")
+ if (medtrumService != null) {
+ aapsLogger.debug(LTag.PUMP, "Medtrum connect - Attempt connection!")
+ val success = medtrumService?.connect(reason) ?: false
+ if (!success) ToastUtils.errorToast(context, info.nightscout.core.ui.R.string.ble_not_supported_or_not_paired)
+ }
+ }
+ }
+
+ override fun disconnect(reason: String) {
+ if (isInitialized()) {
+ aapsLogger.debug(LTag.PUMP, "Medtrum disconnect from: $reason")
+ medtrumService?.disconnect(reason)
+ }
+ }
+
+ override fun stopConnecting() {
+ if (isInitialized()) {
+ aapsLogger.debug(LTag.PUMP, "Medtrum stopConnecting")
+ medtrumService?.stopConnecting()
+ }
+ }
+
+ override fun getPumpStatus(reason: String) {
+ aapsLogger.debug(LTag.PUMP, "Medtrum getPumpStatus - reason:$reason")
+ if (isInitialized()) {
+ val connectionOK = medtrumService?.readPumpStatus() ?: false
+ if (connectionOK == false) {
+ aapsLogger.error(LTag.PUMP, "Medtrum getPumpStatus failed")
+ }
+ }
+ }
+
+ override fun setNewBasalProfile(profile: Profile): PumpEnactResult {
+ // New profile will be set when patch is activated
+ if (!isInitialized()) return PumpEnactResult(injector).success(true).enacted(true)
+
+ return if (medtrumService?.updateBasalsInPump(profile) == true) {
+ rxBus.send(EventDismissNotification(Notification.FAILED_UPDATE_PROFILE))
+ uiInteraction.addNotificationValidFor(Notification.PROFILE_SET_OK, rh.gs(info.nightscout.core.ui.R.string.profile_set_ok), Notification.INFO, 60)
+ PumpEnactResult(injector).success(true).enacted(true)
+ } else {
+ uiInteraction.addNotification(Notification.FAILED_UPDATE_PROFILE, rh.gs(info.nightscout.core.ui.R.string.failed_update_basal_profile), Notification.URGENT)
+ PumpEnactResult(injector)
+ }
+ }
+
+ override fun isThisProfileSet(profile: Profile): Boolean {
+ if (!isInitialized()) return true
+ var result = false
+ val profileBytes = medtrumPump.buildMedtrumProfileArray(profile)
+ if (profileBytes?.size == medtrumPump.actualBasalProfile.size) {
+ result = true
+ for (i in profileBytes.indices) {
+ if (profileBytes[i] != medtrumPump.actualBasalProfile[i]) {
+ result = false
+ break
+ }
+ }
+ }
+ return result
+ }
+
+ override fun lastDataTime(): Long = medtrumPump.lastConnection
+ override val baseBasalRate: Double
+ get() = medtrumPump.baseBasalRate
+
+ override val reservoirLevel: Double
+ get() = medtrumPump.reservoir
+
+ override val batteryLevel: Int
+ get() = 0 // We cannot determine battery level (yet)
+
+ @Synchronized
+ override fun deliverTreatment(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult {
+ aapsLogger.debug(LTag.PUMP, "deliverTreatment: " + detailedBolusInfo.insulin + "U")
+ if (!isInitialized()) return PumpEnactResult(injector).success(false).enacted(false)
+ detailedBolusInfo.insulin = constraintChecker.applyBolusConstraints(Constraint(detailedBolusInfo.insulin)).value()
+ return if (detailedBolusInfo.insulin > 0 && detailedBolusInfo.carbs == 0.0) {
+ aapsLogger.debug(LTag.PUMP, "deliverTreatment: Delivering bolus: " + detailedBolusInfo.insulin + "U")
+ val t = EventOverviewBolusProgress.Treatment(0.0, 0, detailedBolusInfo.bolusType == DetailedBolusInfo.BolusType.SMB, detailedBolusInfo.id)
+ val connectionOK = medtrumService?.setBolus(detailedBolusInfo, t) ?: false
+ val result = PumpEnactResult(injector)
+ result.success = connectionOK && abs(detailedBolusInfo.insulin - t.insulin) < pumpDescription.bolusStep
+ result.bolusDelivered = t.insulin
+ if (!result.success) {
+ // Note: There are no error codes
+ result.comment = "failed"
+ } else {
+ result.comment = "ok"
+ }
+ aapsLogger.debug(LTag.PUMP, "deliverTreatment: OK. Success: ${result.success} Asked: ${detailedBolusInfo.insulin} Delivered: ${result.bolusDelivered}")
+ result
+ } else {
+ aapsLogger.debug(LTag.PUMP, "deliverTreatment: Invalid input")
+ val result = PumpEnactResult(injector)
+ result.success = false
+ result.bolusDelivered = 0.0
+ result.comment = rh.gs(info.nightscout.core.ui.R.string.invalid_input)
+ aapsLogger.error("deliverTreatment: Invalid input")
+ result
+ }
+ }
+
+ override fun stopBolusDelivering() {
+ if (!isInitialized()) return
+
+ aapsLogger.info(LTag.PUMP, "stopBolusDelivering")
+ medtrumService?.stopBolus()
+ }
+
+ @Synchronized
+ override fun setTempBasalAbsolute(absoluteRate: Double, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: PumpSync.TemporaryBasalType): PumpEnactResult {
+ if (!isInitialized()) return PumpEnactResult(injector).success(false).enacted(false)
+
+ aapsLogger.info(LTag.PUMP, "setTempBasalAbsolute - absoluteRate: $absoluteRate, durationInMinutes: $durationInMinutes, enforceNew: $enforceNew")
+ // round rate to pump rate
+ val pumpRate = constraintChecker.applyBasalConstraints(Constraint(absoluteRate), profile).value()
+ temporaryBasalStorage.add(PumpSync.PumpState.TemporaryBasal(dateUtil.now(), T.mins(durationInMinutes.toLong()).msecs(), pumpRate, true, tbrType, 0L, 0L))
+ val connectionOK = medtrumService?.setTempBasal(pumpRate, durationInMinutes) ?: false
+ if (connectionOK
+ && medtrumPump.tempBasalInProgress
+ && Math.abs(medtrumPump.tempBasalAbsoluteRate - pumpRate) <= 0.05
+ ) {
+
+ return PumpEnactResult(injector).success(true).enacted(true).duration(durationInMinutes).absolute(medtrumPump.tempBasalAbsoluteRate)
+ .isPercent(false)
+ .isTempCancel(false)
+ } else {
+ aapsLogger.error(
+ LTag.PUMP,
+ "setTempBasalAbsolute failed, connectionOK: $connectionOK, tempBasalInProgress: ${medtrumPump.tempBasalInProgress}, tempBasalAbsoluteRate: ${medtrumPump.tempBasalAbsoluteRate}"
+ )
+ return PumpEnactResult(injector).success(false).enacted(false).comment("Medtrum setTempBasalAbsolute failed")
+ }
+ }
+
+ override fun setTempBasalPercent(percent: Int, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: PumpSync.TemporaryBasalType): PumpEnactResult {
+ aapsLogger.info(LTag.PUMP, "setTempBasalPercent - percent: $percent, durationInMinutes: $durationInMinutes, enforceNew: $enforceNew")
+ return PumpEnactResult(injector).success(false).enacted(false).comment("Medtrum driver does not support percentage temp basals")
+ }
+
+ override fun setExtendedBolus(insulin: Double, durationInMinutes: Int): PumpEnactResult {
+ aapsLogger.info(LTag.PUMP, "setExtendedBolus - insulin: $insulin, durationInMinutes: $durationInMinutes")
+ return PumpEnactResult(injector).success(false).enacted(false).comment("Medtrum driver does not support extended boluses")
+ }
+
+ override fun cancelTempBasal(enforceNew: Boolean): PumpEnactResult {
+ if (!isInitialized()) return PumpEnactResult(injector).success(false).enacted(false)
+
+ aapsLogger.info(LTag.PUMP, "cancelTempBasal - enforceNew: $enforceNew")
+ val connectionOK = medtrumService?.cancelTempBasal() ?: false
+ if (connectionOK && !medtrumPump.tempBasalInProgress) {
+ return PumpEnactResult(injector).success(true).enacted(true).isTempCancel(true)
+ } else {
+ aapsLogger.error(LTag.PUMP, "cancelTempBasal failed, connectionOK: $connectionOK, tempBasalInProgress: ${medtrumPump.tempBasalInProgress}")
+ return PumpEnactResult(injector).success(false).enacted(false).comment("Medtrum cancelTempBasal failed")
+ }
+ }
+
+ override fun cancelExtendedBolus(): PumpEnactResult {
+ return PumpEnactResult(injector)
+ }
+
+ override fun getJSONStatus(profile: Profile, profileName: String, version: String): JSONObject {
+ val now = System.currentTimeMillis()
+ if (medtrumPump.lastConnection + 60 * 60 * 1000L < System.currentTimeMillis()) {
+ return JSONObject()
+ }
+ val pumpJson = JSONObject()
+ val status = JSONObject()
+ val extended = JSONObject()
+ try {
+ status.put(
+ "status", if (!isSuspended()) "normal"
+ else if (isInitialized() && isSuspended()) "suspended"
+ else "no active patch"
+ )
+ status.put("timestamp", dateUtil.toISOString(medtrumPump.lastConnection))
+ if (medtrumPump.lastBolusTime != 0L) {
+ extended.put("lastBolus", dateUtil.dateAndTimeString(medtrumPump.lastBolusTime))
+ extended.put("lastBolusAmount", medtrumPump.lastBolusAmount)
+ }
+ val tb = pumpSync.expectedPumpState().temporaryBasal
+ if (tb != null) {
+ extended.put("TempBasalAbsoluteRate", tb.convertedToAbsolute(now, profile))
+ extended.put("TempBasalStart", dateUtil.dateAndTimeString(tb.timestamp))
+ extended.put("TempBasalRemaining", tb.plannedRemainingMinutes)
+ }
+ extended.put("BaseBasalRate", baseBasalRate)
+ try {
+ extended.put("ActiveProfile", profileName)
+ } catch (ignored: Exception) {
+ }
+ pumpJson.put("status", status)
+ pumpJson.put("extended", extended)
+ pumpJson.put("reservoir", medtrumPump.reservoir.toInt())
+ pumpJson.put("clock", dateUtil.toISOString(now))
+ } catch (e: JSONException) {
+ aapsLogger.error(LTag.PUMP, "Unhandled exception: $e")
+ }
+ return pumpJson
+ }
+
+ override fun manufacturer(): ManufacturerType {
+ return ManufacturerType.Medtrum
+ }
+
+ override fun model(): PumpType {
+ return medtrumPump.pumpType()
+ }
+
+ override fun serialNumber(): String {
+ // Load from SP here, because this value will be get before pump is initialized
+ return medtrumPump.pumpSNFromSP.toString(radix = 16)
+ }
+
+ override val pumpDescription: PumpDescription
+ get() = PumpDescription(medtrumPump.pumpType())
+
+ override fun shortStatus(veryShort: Boolean): String {
+ var ret = ""
+ if (medtrumPump.lastConnection != 0L) {
+ val agoMillis = System.currentTimeMillis() - medtrumPump.lastConnection
+ val agoMin = (agoMillis / 60.0 / 1000.0).toInt()
+ ret += "LastConn: $agoMin minAgo\n"
+ }
+ if (medtrumPump.lastBolusTime != 0L)
+ ret += "LastBolus: ${DecimalFormatter.to2Decimal(medtrumPump.lastBolusAmount)}U @${DateFormat.format("HH:mm", medtrumPump.lastBolusTime)}\n"
+
+ if (medtrumPump.tempBasalInProgress)
+ ret += "Temp: ${medtrumPump.temporaryBasalToString()}\n"
+
+ ret += "Res: ${DecimalFormatter.to0Decimal(medtrumPump.reservoir)}U\n"
+ return ret
+ }
+
+ override val isFakingTempsByExtendedBoluses: Boolean = false
+
+ override fun loadTDDs(): PumpEnactResult {
+ return PumpEnactResult(injector) // Note: Can implement this if we implement history fully (no priority)
+ }
+
+ override fun getCustomActions(): List? {
+ return null
+ }
+
+ override fun executeCustomAction(customActionType: CustomActionType) {
+ }
+
+ override fun executeCustomCommand(customCommand: CustomCommand): PumpEnactResult? {
+ return null
+ }
+
+ override fun canHandleDST(): Boolean {
+ return true
+ }
+
+ override fun timezoneOrDSTChanged(timeChangeType: TimeChangeType) {
+ medtrumPump.needCheckTimeUpdate = true
+ if (isInitialized()) {
+ commandQueue.updateTime(object : Callback() {
+ override fun run() {
+ if (this.result.success == false) {
+ aapsLogger.error(LTag.PUMP, "Medtrum time update failed")
+ // Only notify here on failure (connection may be failed), service will handle success
+ medtrumService?.timeUpdateNotification(false)
+ }
+ }
+ })
+ }
+ }
+
+ // Medtrum interface
+ override fun loadEvents(): PumpEnactResult {
+ if (!isInitialized()) {
+ val result = PumpEnactResult(injector).success(false)
+ result.comment = "pump not initialized"
+ return result
+ }
+ val connectionOK = medtrumService?.loadEvents() ?: false
+ return PumpEnactResult(injector).success(connectionOK)
+ }
+
+ override fun setUserOptions(): PumpEnactResult {
+ if (!isInitialized()) {
+ val result = PumpEnactResult(injector).success(false)
+ result.comment = "pump not initialized"
+ return result
+ }
+ val connectionOK = medtrumService?.setUserSettings() ?: false
+ return PumpEnactResult(injector).success(connectionOK)
+ }
+
+ override fun clearAlarms(): PumpEnactResult {
+ if (!isInitialized()) {
+ val result = PumpEnactResult(injector).success(false)
+ result.comment = "pump not initialized"
+ return result
+ }
+ val connectionOK = medtrumService?.clearAlarms() ?: false
+ return PumpEnactResult(injector).success(connectionOK)
+ }
+
+ override fun deactivate(): PumpEnactResult {
+ val connectionOK = medtrumService?.deactivatePatch() ?: false
+ return PumpEnactResult(injector).success(connectionOK)
+ }
+
+ override fun updateTime(): PumpEnactResult {
+ if (!isInitialized()) {
+ val result = PumpEnactResult(injector).success(false)
+ result.comment = "pump not initialized"
+ return result
+ }
+ val connectionOK = medtrumService?.updateTimeIfNeeded() ?: false
+ return PumpEnactResult(injector).success(connectionOK)
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPump.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPump.kt
new file mode 100644
index 0000000000..fd24934cda
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPump.kt
@@ -0,0 +1,539 @@
+package info.nightscout.pump.medtrum
+
+import android.util.Base64
+import info.nightscout.interfaces.profile.Profile
+import info.nightscout.interfaces.pump.PumpSync
+import info.nightscout.interfaces.pump.TemporaryBasalStorage
+import info.nightscout.interfaces.pump.defs.PumpType
+import info.nightscout.pump.medtrum.code.ConnectionState
+import info.nightscout.pump.medtrum.comm.enums.AlarmSetting
+import info.nightscout.pump.medtrum.comm.enums.AlarmState
+import info.nightscout.pump.medtrum.comm.enums.BasalType
+import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
+import info.nightscout.pump.medtrum.extension.toByteArray
+import info.nightscout.pump.medtrum.extension.toInt
+import info.nightscout.rx.events.EventOverviewBolusProgress
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.interfaces.ResourceHelper
+import info.nightscout.shared.sharedPreferences.SP
+import info.nightscout.shared.utils.DateUtil
+import info.nightscout.shared.utils.T
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import java.util.*
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlin.math.round
+
+@Singleton
+class MedtrumPump @Inject constructor(
+ private val aapsLogger: AAPSLogger,
+ private val rh: ResourceHelper,
+ private val sp: SP,
+ private val dateUtil: DateUtil,
+ private val pumpSync: PumpSync,
+ private val temporaryBasalStorage: TemporaryBasalStorage
+) {
+
+ companion object {
+
+ const val FAKE_TBR_LENGTH = 4800L
+ }
+
+ // Connection state flow
+ private val _connectionState = MutableStateFlow(ConnectionState.DISCONNECTED)
+ val connectionStateFlow: StateFlow = _connectionState
+ var connectionState: ConnectionState
+ get() = _connectionState.value
+ set(value) {
+ _connectionState.value = value
+ }
+
+ // Pump state flow
+ private val _pumpState = MutableStateFlow(MedtrumPumpState.NONE)
+ val pumpStateFlow: StateFlow = _pumpState
+ var pumpState: MedtrumPumpState
+ get() = _pumpState.value
+ set(value) {
+ _pumpState.value = value
+ sp.putInt(R.string.key_pump_state, value.state.toInt())
+ }
+
+ // Active alarms
+ private var _activeAlarms: EnumSet = EnumSet.noneOf(AlarmState::class.java)
+ var activeAlarms: EnumSet
+ get() = _activeAlarms
+ set(value) {
+ _activeAlarms = value
+ }
+
+ // Prime progress as state flow
+ private val _primeProgress = MutableStateFlow(0)
+ val primeProgressFlow: StateFlow = _primeProgress
+ var primeProgress: Int
+ get() = _primeProgress.value
+ set(value) {
+ _primeProgress.value = value
+ }
+
+ private var _lastBasalType: MutableStateFlow = MutableStateFlow(BasalType.NONE)
+ val lastBasalTypeFlow: StateFlow = _lastBasalType
+ val lastBasalType: BasalType
+ get() = _lastBasalType.value
+
+ private val _lastBasalRate = MutableStateFlow(0.0)
+ val lastBasalRateFlow: StateFlow = _lastBasalRate
+ val lastBasalRate: Double
+ get() = _lastBasalRate.value
+
+ private val _reservoir = MutableStateFlow(0.0)
+ val reservoirFlow: StateFlow = _reservoir
+ var reservoir: Double
+ get() = _reservoir.value
+ set(value) {
+ _reservoir.value = value
+ }
+
+ var batteryVoltage_A = 0.0 // Not used in UI
+ private val _batteryVoltage_B = MutableStateFlow(0.0)
+ val batteryVoltage_BFlow: StateFlow = _batteryVoltage_B
+ var batteryVoltage_B: Double
+ get() = _batteryVoltage_B.value
+ set(value) {
+ _batteryVoltage_B.value = value
+ }
+
+ /** Stuff stored in SP */
+ private var _patchSessionToken = 0L
+ var patchSessionToken: Long
+ get() = _patchSessionToken
+ set(value) {
+ _patchSessionToken = value
+ sp.putLong(R.string.key_session_token, value)
+ }
+
+ private var _patchId = 0L
+ var patchId: Long
+ get() = _patchId
+ set(value) {
+ _patchId = value
+ sp.putLong(R.string.key_patch_id, value)
+ }
+
+ private var _currentSequenceNumber = 0
+ var currentSequenceNumber: Int
+ get() = _currentSequenceNumber
+ set(value) {
+ _currentSequenceNumber = value
+ sp.putInt(R.string.key_current_sequence_number, value)
+ }
+
+ private var _syncedSequenceNumber = 0
+ var syncedSequenceNumber: Int
+ get() = _syncedSequenceNumber
+ set(value) {
+ _syncedSequenceNumber = value
+ sp.putInt(R.string.key_synced_sequence_number, value)
+ }
+
+ private var _actualBasalProfile = byteArrayOf(0)
+ var actualBasalProfile: ByteArray
+ get() = _actualBasalProfile
+ set(value) {
+ _actualBasalProfile = value
+ val encodedString = Base64.encodeToString(value, Base64.DEFAULT)
+ sp.putString(R.string.key_actual_basal_profile, encodedString ?: "")
+ }
+
+ private var _lastBolusTime = 0L // Time in ms!
+ var lastBolusTime: Long
+ get() = _lastBolusTime
+ set(value) {
+ _lastBolusTime = value
+ sp.putLong(R.string.key_last_bolus_time, value)
+ }
+
+ private var _lastBolusAmount = 0.0
+ var lastBolusAmount: Double
+ get() = _lastBolusAmount
+ set(value) {
+ _lastBolusAmount = value
+ sp.putDouble(R.string.key_last_bolus_amount, value)
+ }
+
+ private var _lastConnection = 0L // Time in ms!
+ var lastConnection: Long
+ get() = _lastConnection
+ set(value) {
+ _lastConnection = value
+ sp.putLong(R.string.key_last_connection, value)
+ }
+
+ private var _deviceType: Int = 80 // As reported by pump
+ var deviceType: Int
+ get() = _deviceType
+ set(value) {
+ _deviceType = value
+ sp.putInt(R.string.key_device_type, value)
+ }
+
+ private var _swVersion: String = "" // As reported by pump
+ var swVersion: String
+ get() = _swVersion
+ set(value) {
+ _swVersion = value
+ sp.putString(R.string.key_sw_version, value)
+ }
+
+ private var _patchStartTime = 0L // Time in ms!
+ var patchStartTime: Long
+ get() = _patchStartTime
+ set(value) {
+ _patchStartTime = value
+ sp.putLong(R.string.key_patch_start_time, value)
+ }
+
+ private var _pumpTimeZoneOffset = 0 // As reported by pump
+ var pumpTimeZoneOffset: Int
+ get() = _pumpTimeZoneOffset
+ set(value) {
+ _pumpTimeZoneOffset = value
+ sp.putInt(R.string.key_pump_time_zone_offset, value)
+ }
+
+ private var _pumpSN = 0L
+ val pumpSN: Long
+ get() = _pumpSN
+
+ val pumpSNFromSP: Long
+ get() =
+ try {
+ sp.getString(R.string.key_sn_input, "0").toLong(radix = 16)
+ } catch (e: NumberFormatException) {
+ aapsLogger.debug(LTag.PUMP, "pumpSNFromSP: Invalid input!")
+ 0L
+ }
+
+ var needCheckTimeUpdate = false
+ var lastTimeReceivedFromPump = 0L // Time in ms!
+ var suspendTime = 0L // Time in ms!
+ var patchAge = 0L // Time in seconds?! // As reported by pump, not used (yet)
+
+ // bolus status
+ var bolusingTreatment: EventOverviewBolusProgress.Treatment? = null // actually delivered treatment
+ var bolusAmountToBeDelivered = 0.0 // amount to be delivered
+ var bolusProgressLastTimeStamp: Long = 0 // timestamp of last bolus progress message
+ var bolusStopped = false // bolus stopped by user
+ var bolusDone = false // Bolus completed or stopped on pump
+
+ // Last basal status update (from pump)
+ private var _lastBasalSequence = 0
+ val lastBasalSequence: Int
+ get() = _lastBasalSequence
+
+ private var _lastBasalPatchId = 0L
+ val lastBasalPatchId: Long
+ get() = _lastBasalPatchId
+
+ private var _lastBasalStartTime = 0L
+ val lastBasalStartTime: Long
+ get() = _lastBasalStartTime
+
+ val baseBasalRate: Double
+ get() = getHourlyBasalFromMedtrumProfileArray(actualBasalProfile, dateUtil.now())
+
+ // TBR status
+ val tempBasalInProgress: Boolean
+ get() = lastBasalType == BasalType.ABSOLUTE_TEMP || lastBasalType == BasalType.RELATIVE_TEMP
+ val tempBasalAbsoluteRate: Double
+ get() = if (tempBasalInProgress) lastBasalRate else 0.0
+
+ // Last stop status update
+ var lastStopSequence = 0
+ var lastStopPatchId = 0L
+
+ // User settings (desired values, to be set on pump)
+ var desiredPatchExpiration = false
+ var desiredAlarmSetting = AlarmSetting.LIGHT_VIBRATE_AND_BEEP
+ var desiredHourlyMaxInsulin: Int = 40
+ var desiredDailyMaxInsulin: Int = 180
+
+ init {
+ // Load stuff from SP
+ _patchSessionToken = sp.getLong(R.string.key_session_token, 0L)
+ _lastConnection = sp.getLong(R.string.key_last_connection, 0L)
+ _lastBolusTime = sp.getLong(R.string.key_last_bolus_time, 0L)
+ _lastBolusAmount = sp.getDouble(R.string.key_last_bolus_amount, 0.0)
+ _currentSequenceNumber = sp.getInt(R.string.key_current_sequence_number, 0)
+ _patchId = sp.getLong(R.string.key_patch_id, 0L)
+ _syncedSequenceNumber = sp.getInt(R.string.key_synced_sequence_number, 0)
+ _pumpState.value = MedtrumPumpState.fromByte(sp.getInt(R.string.key_pump_state, MedtrumPumpState.NONE.state.toInt()).toByte())
+ _deviceType = sp.getInt(R.string.key_device_type, 0)
+ _swVersion = sp.getString(R.string.key_sw_version, "")
+ _patchStartTime = sp.getLong(R.string.key_patch_start_time, 0L)
+ _pumpTimeZoneOffset = sp.getInt(R.string.key_pump_time_zone_offset, 0)
+
+ loadActiveAlarms()
+
+ val encodedString = sp.getString(R.string.key_actual_basal_profile, "0")
+ try {
+ _actualBasalProfile = Base64.decode(encodedString, Base64.DEFAULT)
+ } catch (e: Exception) {
+ aapsLogger.error(LTag.PUMP, "Error decoding basal profile from SP: $encodedString")
+ }
+ }
+
+ fun pumpType(): PumpType =
+ when (deviceType) {
+ 80, 88 -> PumpType.MEDTRUM_NANO
+ else -> PumpType.MEDTRUM_UNTESTED
+ }
+
+ fun loadUserSettingsFromSP() {
+ desiredPatchExpiration = sp.getBoolean(info.nightscout.pump.medtrum.R.string.key_patch_expiration, false)
+ val alarmSettingCode = sp.getString(info.nightscout.pump.medtrum.R.string.key_alarm_setting, AlarmSetting.LIGHT_VIBRATE_AND_BEEP.code.toString()).toByte()
+ desiredAlarmSetting = AlarmSetting.values().firstOrNull { it.code == alarmSettingCode } ?: AlarmSetting.LIGHT_VIBRATE_AND_BEEP
+ desiredHourlyMaxInsulin = sp.getInt(info.nightscout.pump.medtrum.R.string.key_hourly_max_insulin, 40)
+ desiredDailyMaxInsulin = sp.getInt(info.nightscout.pump.medtrum.R.string.key_daily_max_insulin, 180)
+ _pumpSN = pumpSNFromSP
+
+ }
+
+ fun buildMedtrumProfileArray(nsProfile: Profile): ByteArray? {
+ val list = nsProfile.getBasalValues()
+ var basals = byteArrayOf()
+ for (item in list) {
+ val rate = round(item.value / 0.05).toInt()
+ val time = item.timeAsSeconds / 60
+ if (rate > 0xFFF || time > 0xFFF) {
+ aapsLogger.error(LTag.PUMP, "buildMedtrumProfileArray: rate or time too large: $rate, $time")
+ return null
+ }
+ basals += ((rate shl 12) + time).toByteArray(3)
+ aapsLogger.debug(LTag.PUMP, "buildMedtrumProfileArray: value: ${item.value} time: ${item.timeAsSeconds}, converted: $rate, $time")
+ }
+ return (list.size).toByteArray(1) + basals
+ }
+
+ fun getHourlyBasalFromMedtrumProfileArray(basalProfile: ByteArray, timestamp: Long): Double {
+ val basalCount = basalProfile[0].toInt()
+ var basal = 0.0
+ if (basalProfile.size < 4 || (basalProfile.size - 1) % 3 != 0 || basalCount > 24) {
+ aapsLogger.debug(LTag.PUMP, "getHourlyBasalFromMedtrumProfileArray: No valid basal profile set")
+ return basal
+ }
+
+ val date = GregorianCalendar()
+ date.timeInMillis = timestamp
+ val hourOfDayMinutes = date.get(GregorianCalendar.HOUR_OF_DAY) * 60 + date.get(GregorianCalendar.MINUTE)
+
+ for (index in 0 until basalCount) {
+ val currentIndex = 1 + (index * 3)
+ val nextIndex = currentIndex + 3
+ val rateAndTime = basalProfile.copyOfRange(currentIndex, nextIndex).toInt()
+ val rate = (rateAndTime shr 12) * 0.05
+ val startMinutes = rateAndTime and 0xFFF
+
+ val endMinutes = if (nextIndex < basalProfile.size) {
+ val nextRateAndTime = basalProfile.copyOfRange(nextIndex, nextIndex + 3).toInt()
+ nextRateAndTime and 0xFFF
+ } else {
+ 24 * 60
+ }
+
+ if (hourOfDayMinutes in startMinutes until endMinutes) {
+ basal = rate
+ aapsLogger.debug(LTag.PUMP, "getHourlyBasalFromMedtrumProfileArray: basal: $basal")
+ break
+ }
+ // aapsLogger.debug(LTag.PUMP, "getHourlyBasalFromMedtrumProfileArray: rate: $rate, startMinutes: $startMinutes, endMinutes: $endMinutes")
+ }
+ return basal
+ }
+
+ fun handleBolusStatusUpdate(bolusType: Int, bolusCompleted: Boolean, amountDelivered: Double) {
+ aapsLogger.debug(LTag.PUMP, "handleBolusStatusUpdate: bolusType: $bolusType bolusCompleted: $bolusCompleted amountDelivered: $amountDelivered")
+ bolusProgressLastTimeStamp = dateUtil.now()
+ bolusingTreatment?.insulin = amountDelivered
+ bolusDone = bolusCompleted
+ }
+
+ fun handleBasalStatusUpdate(basalType: BasalType, basalValue: Double, basalSequence: Int, basalPatchId: Long, basalStartTime: Long) {
+ handleBasalStatusUpdate(basalType, basalValue, basalSequence, basalPatchId, basalStartTime, dateUtil.now())
+ }
+
+ fun handleBasalStatusUpdate(basalType: BasalType, basalRate: Double, basalSequence: Int, basalPatchId: Long, basalStartTime: Long, receivedTime: Long) {
+ aapsLogger.debug(
+ LTag.PUMP,
+ "handleBasalStatusUpdate: basalType: $basalType basalValue: $basalRate basalSequence: $basalSequence basalPatchId: $basalPatchId basalStartTime: $basalStartTime " + "receivedTime: $receivedTime"
+ )
+ @Suppress("UNNECESSARY_SAFE_CALL") // Safe call to allow mocks to return null
+ val expectedTemporaryBasal = pumpSync.expectedPumpState()?.temporaryBasal
+ if (basalType.isTempBasal() && expectedTemporaryBasal?.pumpId != basalStartTime) {
+ // Note: temporaryBasalInfo will be removed from temporaryBasalStorage after this call
+ val temporaryBasalInfo = temporaryBasalStorage.findTemporaryBasal(basalStartTime, basalRate)
+
+ // If duration is unknown, no way to get it now, set patch lifetime as duration
+ val duration = temporaryBasalInfo?.duration ?: T.mins(FAKE_TBR_LENGTH).msecs()
+ val adjustedBasalRate = if (basalType == BasalType.ABSOLUTE_TEMP) {
+ basalRate
+ } else {
+ (basalRate / baseBasalRate) * 100 // calculate the percentage of the original basal rate
+ }
+ val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
+ timestamp = basalStartTime,
+ rate = adjustedBasalRate,
+ duration = duration,
+ isAbsolute = (basalType == BasalType.ABSOLUTE_TEMP),
+ type = temporaryBasalInfo?.type,
+ pumpId = basalStartTime,
+ pumpType = pumpType(),
+ pumpSerial = pumpSN.toString(radix = 16)
+ )
+ aapsLogger.debug(
+ LTag.PUMPCOMM,
+ "handleBasalStatusUpdate: ${if (newRecord) "**NEW** " else ""}EVENT TEMP_START ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) " + "Rate: $basalRate Duration: ${duration} temporaryBasalInfo: $temporaryBasalInfo, expectedTemporaryBasal: $expectedTemporaryBasal"
+ )
+ } else if (basalType.isSuspendedByPump() && expectedTemporaryBasal?.pumpId != basalStartTime) {
+ val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
+ timestamp = basalStartTime,
+ rate = 0.0,
+ duration = T.mins(FAKE_TBR_LENGTH).msecs(),
+ isAbsolute = true,
+ type = PumpSync.TemporaryBasalType.PUMP_SUSPEND,
+ pumpId = basalStartTime,
+ pumpType = pumpType(),
+ pumpSerial = pumpSN.toString(radix = 16)
+ )
+ aapsLogger.debug(
+ LTag.PUMPCOMM,
+ "handleBasalStatusUpdate: ${if (newRecord) "**NEW** " else ""}EVENT TEMP_START ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) expectedTemporaryBasal: $expectedTemporaryBasal"
+ )
+ } else if (basalType == BasalType.NONE && expectedTemporaryBasal?.rate != basalRate && expectedTemporaryBasal?.duration != T.mins(FAKE_TBR_LENGTH).msecs()) {
+ // Pump suspended, set fake TBR
+ setFakeTBR()
+ } else if (basalType == BasalType.STANDARD) {
+ if (expectedTemporaryBasal != null) {
+ // Pump resumed, sync end
+ val success = pumpSync.syncStopTemporaryBasalWithPumpId(
+ timestamp = basalStartTime + 250, // Time of normal basal start = time of tbr end
+ endPumpId = basalStartTime + 250, // +250ms Make sure there is time between start and stop of TBR
+ pumpType = pumpType(),
+ pumpSerial = pumpSN.toString(radix = 16)
+ )
+ aapsLogger.debug(LTag.PUMPCOMM, "handleBasalStatusUpdate: EVENT TEMP_END ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) success: $success")
+ }
+ }
+
+ // Update medtrum pump state
+ _lastBasalType.value = basalType
+ _lastBasalRate.value = basalRate
+ _lastBasalSequence = basalSequence
+ if (basalSequence > currentSequenceNumber) {
+ currentSequenceNumber = basalSequence
+ }
+ _lastBasalPatchId = basalPatchId
+ if (basalPatchId != patchId) {
+ aapsLogger.error(LTag.PUMP, "handleBasalStatusUpdate: WTF? PatchId in status update does not match current patchId!")
+ }
+ _lastBasalStartTime = basalStartTime
+ }
+
+ fun handleStopStatusUpdate(stopSequence: Int, stopPatchId: Long) {
+ aapsLogger.debug(LTag.PUMP, "handleStopStatusUpdate: stopSequence: $stopSequence stopPatchId: $stopPatchId")
+ lastStopSequence = stopSequence
+ if (stopSequence > currentSequenceNumber) {
+ currentSequenceNumber = stopSequence
+ }
+ lastStopPatchId = stopPatchId
+ if (stopPatchId != patchId) {
+ aapsLogger.error(LTag.PUMP, "handleStopStatusUpdate: WTF? PatchId in status update does not match current patchId!")
+ }
+ }
+
+ fun setFakeTBRIfNeeded() {
+ val expectedTemporaryBasal = pumpSync.expectedPumpState().temporaryBasal
+ if (expectedTemporaryBasal?.duration != T.mins(FAKE_TBR_LENGTH).msecs()) {
+ setFakeTBR()
+ }
+ }
+
+ private fun setFakeTBR() {
+ val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
+ timestamp = dateUtil.now(),
+ rate = 0.0,
+ duration = T.mins(FAKE_TBR_LENGTH).msecs(),
+ isAbsolute = true,
+ type = PumpSync.TemporaryBasalType.PUMP_SUSPEND,
+ pumpId = dateUtil.now(),
+ pumpType = pumpType(),
+ pumpSerial = pumpSN.toString(radix = 16)
+ )
+ aapsLogger.debug(
+ LTag.PUMPCOMM,
+ "handleBasalStatusUpdate: ${if (newRecord) "**NEW** " else ""}EVENT TEMP_START (FAKE)"
+ )
+ }
+
+ fun temporaryBasalToString(): String {
+ if (!tempBasalInProgress) return ""
+ return tempBasalAbsoluteRate.toString() + "U/h"
+ }
+
+ fun addAlarm(alarm: AlarmState) {
+ activeAlarms.add(alarm)
+ saveActiveAlarms()
+ }
+
+ fun removeAlarm(alarm: AlarmState) {
+ activeAlarms.remove(alarm)
+ saveActiveAlarms()
+ }
+
+ fun clearAlarmState() {
+ activeAlarms.clear()
+ saveActiveAlarms()
+ }
+
+ fun alarmStateToString(alarmState: AlarmState): String {
+ val stringId = when (alarmState) {
+ AlarmState.NONE -> R.string.alarm_none
+ AlarmState.PUMP_LOW_BATTERY -> R.string.alarm_pump_low_battery
+ AlarmState.PUMP_LOW_RESERVOIR -> R.string.alarm_pump_low_reservoir
+ AlarmState.PUMP_EXPIRES_SOON -> R.string.alarm_pump_expires_soon
+ AlarmState.LOWBG_SUSPENDED -> R.string.alarm_lowbg_suspended
+ AlarmState.LOWBG_SUSPENDED2 -> R.string.alarm_lowbg_suspended2
+ AlarmState.AUTO_SUSPENDED -> R.string.alarm_auto_suspended
+ AlarmState.HMAX_SUSPENDED -> R.string.alarm_hmax_suspended
+ AlarmState.DMAX_SUSPENDED -> R.string.alarm_dmax_suspended
+ AlarmState.SUSPENDED -> R.string.alarm_suspended
+ AlarmState.PAUSED -> R.string.alarm_paused
+ AlarmState.OCCLUSION -> R.string.alarm_occlusion
+ AlarmState.EXPIRED -> R.string.alarm_expired
+ AlarmState.RESERVOIR_EMPTY -> R.string.alarm_reservoir_empty
+ AlarmState.PATCH_FAULT -> R.string.alarm_patch_fault
+ AlarmState.PATCH_FAULT2 -> R.string.alarm_patch_fault2
+ AlarmState.BASE_FAULT -> R.string.alarm_base_fault
+ AlarmState.BATTERY_OUT -> R.string.alarm_battery_out
+ AlarmState.NO_CALIBRATION -> R.string.alarm_no_calibration
+ }
+ return rh.gs(stringId)
+ }
+
+ private fun saveActiveAlarms() {
+ val alarmsStr = activeAlarms.joinToString(separator = ",") { it.name }
+ sp.putString(R.string.key_active_alarms, alarmsStr)
+ }
+
+ private fun loadActiveAlarms() {
+ val alarmsStr = sp.getString(R.string.key_active_alarms, "")
+ if (alarmsStr.isNullOrEmpty()) {
+ activeAlarms = EnumSet.noneOf(AlarmState::class.java)
+ } else {
+ activeAlarms = alarmsStr.split(",")
+ .mapNotNull { AlarmState.values().find { alarm -> alarm.name == it } }
+ .let { EnumSet.copyOf(it) }
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/bindingadapters/OnSafeClickListener.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/bindingadapters/OnSafeClickListener.kt
new file mode 100644
index 0000000000..4475938960
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/bindingadapters/OnSafeClickListener.kt
@@ -0,0 +1,26 @@
+package info.nightscout.pump.medtrum.bindingadapters
+
+import android.view.View
+import java.util.concurrent.atomic.AtomicBoolean
+
+class OnSafeClickListener(
+ private val clickListener: View.OnClickListener,
+ private val intervalMs: Long = MIN_CLICK_INTERVAL
+) : View.OnClickListener {
+ private var canClick = AtomicBoolean(true)
+
+ override fun onClick(v: View?) {
+ if (canClick.getAndSet(false)) {
+ v?.run {
+ postDelayed({
+ canClick.set(true)
+ }, intervalMs)
+ clickListener.onClick(v)
+ }
+ }
+ }
+ companion object {
+ // Set duplicate click prevention time
+ private const val MIN_CLICK_INTERVAL: Long = 1000
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/bindingadapters/ViewBindingAdapter.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/bindingadapters/ViewBindingAdapter.kt
new file mode 100644
index 0000000000..f4b51efe1a
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/bindingadapters/ViewBindingAdapter.kt
@@ -0,0 +1,29 @@
+package info.nightscout.pump.medtrum.bindingadapters
+
+import android.view.View
+import android.widget.TextView
+import androidx.annotation.ColorRes
+import androidx.databinding.BindingAdapter
+import info.nightscout.pump.medtrum.extension.setVisibleOrGone
+
+@BindingAdapter("android:visibility")
+fun setVisibility(view: View, visible: Boolean) {
+ view.setVisibleOrGone(visible)
+}
+
+@BindingAdapter("visibleOrGone")
+fun setVisibleOrGone(view: View, visibleOrGone: Boolean) {
+ view.setVisibleOrGone(visibleOrGone)
+}
+
+@BindingAdapter("onSafeClick")
+fun View.setOnSafeClickListener(clickListener: View.OnClickListener?) {
+ clickListener?.also {
+ setOnClickListener(OnSafeClickListener(it))
+ } ?: setOnClickListener(null)
+}
+
+@BindingAdapter("textColor")
+fun setTextColor(view: TextView, @ColorRes colorResId: Int) {
+ view.setTextColor(view.context.getColor(colorResId))
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/code/ConnectionState.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/code/ConnectionState.kt
new file mode 100644
index 0000000000..2b51faf0f0
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/code/ConnectionState.kt
@@ -0,0 +1,8 @@
+package info.nightscout.pump.medtrum.code
+
+enum class ConnectionState {
+ CONNECTED,
+ DISCONNECTED,
+ CONNECTING,
+ DISCONNECTING;
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/code/EventType.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/code/EventType.kt
new file mode 100644
index 0000000000..bd8f902afd
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/code/EventType.kt
@@ -0,0 +1,7 @@
+package info.nightscout.pump.medtrum.code
+
+enum class EventType {
+ CHANGE_PATCH_CLICKED,
+ PROFILE_NOT_SET,
+ ;
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/code/PatchStep.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/code/PatchStep.kt
new file mode 100644
index 0000000000..ea0cdf3d28
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/code/PatchStep.kt
@@ -0,0 +1,21 @@
+package info.nightscout.pump.medtrum.code
+
+enum class PatchStep {
+ START_DEACTIVATION,
+ DEACTIVATE,
+ FORCE_DEACTIVATION,
+ DEACTIVATION_COMPLETE,
+ PREPARE_PATCH,
+ PREPARE_PATCH_CONNECT,
+ PRIME,
+ PRIMING,
+ PRIME_COMPLETE,
+ ATTACH_PATCH,
+ ACTIVATE,
+ ACTIVATE_COMPLETE,
+ RETRY_ACTIVATION,
+ RETRY_ACTIVATION_CONNECT,
+ ERROR,
+ CANCEL,
+ COMPLETE;
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/ManufacturerData.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/ManufacturerData.kt
new file mode 100644
index 0000000000..098d31d984
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/ManufacturerData.kt
@@ -0,0 +1,36 @@
+package info.nightscout.pump.medtrum.comm
+
+import kotlin.experimental.and
+import info.nightscout.pump.medtrum.extension.toLong
+
+class ManufacturerData(private val manufacturerDataBytes: ByteArray) {
+ private var deviceID: Long = 0
+ private var deviceType = 0
+ private var version = 0
+
+ init {
+ setData(manufacturerDataBytes)
+ }
+
+ fun setData(inputData: ByteArray) {
+ var index = 0
+ val deviceIDBytes: ByteArray = inputData.copyOfRange(index, index + 4)
+ deviceID = deviceIDBytes.toLong()
+ index += 4
+ deviceType = (inputData[index] and 0xff.toByte()).toInt()
+ index += 1
+ version = (inputData[index] and 0xff.toByte()).toInt()
+ }
+
+ fun getDeviceSN(): Long{
+ return deviceID
+ }
+
+ fun getDeviceType(): Int {
+ return deviceType
+ }
+
+ fun getVersion(): Int {
+ return version
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/ReadDataPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/ReadDataPacket.kt
new file mode 100644
index 0000000000..e091cb8f73
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/ReadDataPacket.kt
@@ -0,0 +1,19 @@
+package info.nightscout.pump.medtrum.comm
+
+class ReadDataPacket(data: ByteArray) {
+
+ private var totalData = data.copyOfRange(0, data.size - 1) // Strip crc
+ private var dataSize: Byte = data[0]
+
+ fun addData(newData: ByteArray) {
+ totalData += newData.copyOfRange(4, newData.size - 1) // Strip header and crc
+ }
+
+ fun allDataReceived(): Boolean {
+ return (totalData.size >= dataSize)
+ }
+
+ fun getData(): ByteArray {
+ return totalData
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/WriteCommandPackets.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/WriteCommandPackets.kt
new file mode 100644
index 0000000000..ae0f18e9f4
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/WriteCommandPackets.kt
@@ -0,0 +1,65 @@
+package info.nightscout.pump.medtrum.comm
+
+class WriteCommandPackets(data: ByteArray, sequenceNumber: Int) {
+
+ private val CRC_8_TABLE: IntArray = intArrayOf(0, 155, 173, 54, 193, 90, 108, 247, 25, 130, 180, 47, 216, 67, 117, 238, 50, 169, 159, 4, 243, 104, 94, 197, 43, 176, 134, 29, 234, 113, 71, 220, 100, 255, 201, 82, 165, 62, 8, 147, 125, 230, 208, 75, 188, 39, 17, 138, 86, 205, 251, 96, 151, 12, 58, 161, 79, 212, 226, 121, 142, 21, 35, 184, 200, 83, 101, 254, 9, 146, 164, 63, 209, 74, 124, 231, 16, 139, 189, 38, 250, 97, 87, 204, 59, 160, 150, 13, 227, 120, 78, 213, 34, 185, 143, 20, 172, 55, 1, 154, 109, 246, 192, 91, 181, 46, 24, 131, 116, 239, 217, 66, 158, 5, 51, 168, 95, 196, 242, 105, 135, 28, 42, 177, 70, 221, 235, 112, 11, 144, 166, 61, 202, 81, 103, 252, 18, 137, 191, 36, 211, 72, 126, 229, 57, 162, 148, 15, 248, 99, 85, 206, 32, 187, 141, 22, 225, 122, 76, 215, 111, 244, 194, 89, 174, 53, 3, 152, 118, 237, 219, 64, 183, 44, 26, 129, 93, 198, 240, 107, 156, 7, 49, 170, 68, 223, 233, 114, 133, 30, 40, 179, 195, 88, 110, 245, 2, 153, 175, 52, 218, 65, 119, 236, 27, 128, 182, 45, 241, 106, 92, 199, 48, 171, 157, 6, 232, 115, 69, 222, 41, 178, 132, 31, 167, 60, 10, 145, 102, 253, 203, 80, 190, 37, 19, 136, 127, 228, 210, 73, 149, 14, 56, 163, 84, 207, 249, 98, 140, 23, 33, 186, 77, 214, 224, 123)
+
+ private val packages = mutableListOf()
+ private var index = 0
+
+ init {
+ // PackageIndex: 0 initially, if there are multiple packets, for the first packet it is set to 0 (not included in CRC calculation but sent in actual header)
+ var pkgIndex = 0
+ var header = byteArrayOf(
+ (data.size + 4).toByte(),
+ data[0],
+ sequenceNumber.toByte(),
+ pkgIndex.toByte()
+ )
+
+ var tmp: ByteArray = header + data.copyOfRange(1, data.size)
+ var totalCommand: ByteArray = tmp + calcCrc8(tmp, tmp.size).toByte()
+
+ if ((totalCommand.size - header.size) <= 15) {
+ packages.add(totalCommand + 0.toByte())
+ } else {
+ pkgIndex = 1
+ var remainingCommand = totalCommand.copyOfRange(4, totalCommand.size)
+
+ while (remainingCommand.size > 15) {
+ header[3] = pkgIndex.toByte()
+ tmp = header + remainingCommand.copyOfRange(0, 15)
+ packages.add(tmp + calcCrc8(tmp, tmp.size).toByte())
+
+ remainingCommand = remainingCommand.copyOfRange(15, remainingCommand.size)
+ pkgIndex = (pkgIndex + 1) % 256
+ }
+
+ // Add last package
+ header[3] = pkgIndex.toByte()
+ tmp = header + remainingCommand
+ packages.add(tmp + calcCrc8(tmp, tmp.size).toByte())
+ }
+ }
+
+ fun getNextPacket(): ByteArray? {
+ var ret: ByteArray? = null
+ if (index < packages.size) {
+ ret = packages[index]
+ index++
+ }
+ return ret
+ }
+
+ fun allPacketsConsumed(): Boolean {
+ return !(index < packages.size)
+ }
+
+ private fun calcCrc8(value: ByteArray, size: Int): Int {
+ var crc8 = 0
+ for (i in 0 until size) {
+ crc8 = CRC_8_TABLE[(value[i].toInt() and 255) xor (crc8 and 255)].toInt() and 255
+ }
+ return crc8
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/AlarmSetting.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/AlarmSetting.kt
new file mode 100644
index 0000000000..83557b4ff3
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/AlarmSetting.kt
@@ -0,0 +1,12 @@
+package info.nightscout.pump.medtrum.comm.enums
+
+enum class AlarmSetting(val code: Byte) {
+ LIGHT_VIBRATE_AND_BEEP(0),
+ LIGHT_AND_VIBRATE(1),
+ LIGHT_AND_BEEP(2),
+ LIGHT_ONLY(3),
+ VIBRATE_AND_BEEP(4),
+ VIBRATE_ONLY(5),
+ BEEP_ONLY(6),
+ NONE(7)
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/AlarmState.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/AlarmState.kt
new file mode 100644
index 0000000000..50ae4e30f6
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/AlarmState.kt
@@ -0,0 +1,23 @@
+package info.nightscout.pump.medtrum.comm.enums
+
+enum class AlarmState {
+ NONE,
+ PUMP_LOW_BATTERY, // Mapped from error flag 1
+ PUMP_LOW_RESERVOIR, // Mapped from error flag 2
+ PUMP_EXPIRES_SOON, // Mapped from error flag 3
+ LOWBG_SUSPENDED, // Mapped from pump status 64
+ LOWBG_SUSPENDED2, // Mapped from pump status 65
+ AUTO_SUSPENDED, // Mapped from pump status 66
+ HMAX_SUSPENDED, // Mapped from pump status 67
+ DMAX_SUSPENDED, // Mapped from pump status 68
+ SUSPENDED, // Mapped from pump status 69
+ PAUSED, // Mapped from pump status 70
+ OCCLUSION, // Mapped from pump status 96
+ EXPIRED, // Mapped from pump status 97
+ RESERVOIR_EMPTY, // Mapped from pump status 98
+ PATCH_FAULT, // Mapped from pump status 99
+ PATCH_FAULT2, // Mapped from pump status 100
+ BASE_FAULT, // Mapped from pump status 101
+ BATTERY_OUT, // Mapped from pump status 102
+ NO_CALIBRATION // Mapped from pump status 103
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/BasalType.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/BasalType.kt
new file mode 100644
index 0000000000..f35ff3b4ba
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/BasalType.kt
@@ -0,0 +1,54 @@
+package info.nightscout.pump.medtrum.comm.enums
+
+enum class BasalType {
+ NONE,
+ STANDARD,
+ EXERCISE,
+ HOLIDAY,
+ PROGRAM_A,
+ PROGRAM_B,
+ ABSOLUTE_TEMP,
+ RELATIVE_TEMP,
+ PROGRAM_C,
+ PROGRAM_D,
+ SICK,
+ AUTO,
+ NEW,
+ SUSPEND_LOW_GLUCOSE,
+ SUSPEND_PREDICT_LOW_GLUCOSE,
+ SUSPEND_AUTO,
+ SUSPEND_MORE_THAN_MAX_PER_HOUR,
+ SUSPEND_MORE_THAN_MAX_PER_DAY,
+ SUSPEND_MANUAL,
+ SUSPEND_KEY_LOST,
+ STOP_OCCLUSION,
+ STOP_EXPIRED,
+ STOP_EMPTY,
+ STOP_PATCH_FAULT,
+ STOP_PATCH_FAULT2,
+ STOP_BASE_FAULT,
+ STOP_DISCARD,
+ STOP_BATTERY_EXHAUSTED,
+ STOP,
+ PAUSE_INTERRUPT,
+ PRIME,
+ AUTO_MODE_START,
+ AUTO_MODE_EXIT,
+ AUTO_MODE_TARGET_100,
+ AUTO_MODE_TARGET_110,
+ AUTO_MODE_TARGET_120,
+ AUTO_MODE_BREAKFAST,
+ AUTO_MODE_LUNCH,
+ AUTO_MODE_DINNER,
+ AUTO_MODE_SNACK,
+ AUTO_MODE_EXERCISE_START,
+ AUTO_MODE_EXERCISE_EXIT;
+
+ fun isTempBasal(): Boolean {
+ return this == ABSOLUTE_TEMP || this == RELATIVE_TEMP
+ }
+
+ fun isSuspendedByPump(): Boolean {
+ return this in SUSPEND_LOW_GLUCOSE..STOP
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/BolusType.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/BolusType.kt
new file mode 100644
index 0000000000..7bf88c9f53
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/BolusType.kt
@@ -0,0 +1,8 @@
+package info.nightscout.pump.medtrum.comm.enums
+
+enum class BolusType {
+ NONE,
+ NORMAL,
+ EXTENDED,
+ COMBI;
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/CommandType.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/CommandType.kt
new file mode 100644
index 0000000000..035174e815
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/CommandType.kt
@@ -0,0 +1,26 @@
+package info.nightscout.pump.medtrum.comm.enums
+
+enum class CommandType(val code: Byte) {
+ SYNCHRONIZE(3),
+ SUBSCRIBE(4),
+ AUTH_REQ(5),
+ GET_DEVICE_TYPE(6),
+ SET_TIME(10),
+ GET_TIME(11),
+ SET_TIME_ZONE(12),
+ PRIME(16),
+ ACTIVATE(18),
+ SET_BOLUS(19),
+ CANCEL_BOLUS(20),
+ SET_BASAL_PROFILE(21),
+ SET_TEMP_BASAL(24),
+ CANCEL_TEMP_BASAL(25),
+ RESUME_PUMP(29),
+ POLL_PATCH(30),
+ STOP_PATCH(31),
+ READ_BOLUS_STATE(34),
+ SET_PATCH(35),
+ SET_BOLUS_MOTOR(36),
+ GET_RECORD(99),
+ CLEAR_ALARM(115)
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/MedtrumPumpState.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/MedtrumPumpState.kt
new file mode 100644
index 0000000000..b52b9c1793
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/MedtrumPumpState.kt
@@ -0,0 +1,34 @@
+package info.nightscout.pump.medtrum.comm.enums
+
+enum class MedtrumPumpState(val state: Byte) {
+ NONE(0),
+ IDLE(1),
+ FILLED(2),
+ PRIMING(3),
+ PRIMED(4),
+ EJECTING(5),
+ EJECTED(6),
+ ACTIVE(32),
+ ACTIVE_ALT(33),
+ LOWBG_SUSPENDED(64),
+ LOWBG_SUSPENDED2(65),
+ AUTO_SUSPENDED(66),
+ HMAX_SUSPENDED(67),
+ DMAX_SUSPENDED(68),
+ SUSPENDED(69),
+ PAUSED(70),
+ OCCLUSION(96),
+ EXPIRED(97),
+ RESERVOIR_EMPTY(98),
+ PATCH_FAULT(99),
+ PATCH_FAULT2(100),
+ BASE_FAULT(101),
+ BATTERY_OUT(102),
+ NO_CALIBRATION(103),
+ STOPPED(128.toByte());
+
+ companion object {
+ fun fromByte(state: Byte) = values().find { it.state == state }
+ ?: throw IllegalAccessException("")
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/ActivatePacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/ActivatePacket.kt
new file mode 100644
index 0000000000..838fff9727
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/ActivatePacket.kt
@@ -0,0 +1,122 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.interfaces.pump.DetailedBolusInfo
+import info.nightscout.interfaces.pump.PumpSync
+import info.nightscout.pump.medtrum.MedtrumPump
+import info.nightscout.pump.medtrum.comm.enums.CommandType.ACTIVATE
+import info.nightscout.pump.medtrum.comm.enums.BasalType
+import info.nightscout.pump.medtrum.extension.toByteArray
+import info.nightscout.pump.medtrum.extension.toByte
+import info.nightscout.interfaces.stats.TddCalculator
+import info.nightscout.pump.medtrum.extension.toInt
+import info.nightscout.pump.medtrum.extension.toLong
+import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
+import javax.inject.Inject
+import kotlin.math.round
+
+class ActivatePacket(injector: HasAndroidInjector, private val basalProfile: ByteArray) : MedtrumPacket(injector) {
+
+ @Inject lateinit var medtrumPump: MedtrumPump
+ @Inject lateinit var tddCalculator: TddCalculator
+ @Inject lateinit var pumpSync: PumpSync
+
+ companion object {
+
+ private const val RESP_PATCH_ID_START = 6
+ private const val RESP_PATCH_ID_END = RESP_PATCH_ID_START + 4
+ private const val RESP_TIME_START = 10
+ private const val RESP_TIME_END = RESP_TIME_START + 4
+ private const val RESP_BASAL_TYPE_START = 14
+ private const val RESP_BASAL_TYPE_END = RESP_BASAL_TYPE_START + 1
+ private const val RESP_BASAL_VALUE_START = 15
+ private const val RESP_BASAL_VALUE_END = RESP_BASAL_VALUE_START + 2
+ private const val RESP_BASAL_SEQUENCE_START = 17
+ private const val RESP_BASAL_SEQUENCE_END = RESP_BASAL_SEQUENCE_START + 2
+ private const val RESP_BASAL_PATCH_ID_START = 19
+ private const val RESP_BASAL_PATCH_ID_END = RESP_BASAL_PATCH_ID_START + 2
+ private const val RESP_BASAL_START_TIME_START = 21
+ private const val RESP_BASAL_START_TIME_END = RESP_BASAL_START_TIME_START + 4
+ }
+
+ init {
+ opCode = ACTIVATE.code
+ expectedMinRespLength = RESP_BASAL_START_TIME_END
+ }
+
+ override fun getRequest(): ByteArray {
+ /**
+ * byte 0: opCode
+ * byte 1: autoSuspendEnable // Value for auto mode, not used for AAPS
+ * byte 2: autoSuspendTime // Value for auto mode, not used for AAPS
+ * byte 3: expirationTimer // Expiration timer, 0 = no expiration 1 = 12 hour reminder and expiration after 3 days
+ * byte 4: alarmSetting // See AlarmSetting
+ * byte 5: lowSuspend // Value for auto mode, not used for AAPS
+ * byte 6: predictiveLowSuspend // Value for auto mode, not used for AAPS
+ * byte 7: predictiveLowSuspendRange // Value for auto mode, not used for AAPS
+ * byte 8-9: hourlyMaxInsulin // Max hourly dose of insulin, divided by 0.05
+ * byte 10-11: daylyMaxSet // Max daily dose of insulin, divided by 0.05
+ * byte 12-13: tddToday // Current TDD (of present day), divided by 0.05
+ * byte 14: 1 // Always 1
+ * bytes 15 - end // Basal profile > see MedtrumPump
+ */
+
+ val autoSuspendEnable: Byte = 0
+ val autoSuspendTime: Byte = 12 // Not sure why, but pump needs this in order to activate
+
+ val patchExpiration: Byte = medtrumPump.desiredPatchExpiration.toByte()
+ val alarmSetting: Byte = medtrumPump.desiredAlarmSetting.code
+
+ val lowSuspend: Byte = 0
+ val predictiveLowSuspend: Byte = 0
+ val predictiveLowSuspendRange: Byte = 30 // Not sure why, but pump needs this in order to activate
+
+ val hourlyMaxInsulin: Int = round(medtrumPump.desiredHourlyMaxInsulin / 0.05).toInt()
+ val dailyMaxInsulin: Int = round(medtrumPump.desiredDailyMaxInsulin / 0.05).toInt()
+ val currentTDD: Double = tddCalculator.calculateToday()?.totalAmount?.div(0.05) ?: 0.0
+
+ return byteArrayOf(opCode) + autoSuspendEnable + autoSuspendTime + patchExpiration + alarmSetting + lowSuspend + predictiveLowSuspend + predictiveLowSuspendRange + hourlyMaxInsulin.toByteArray(
+ 2
+ ) + dailyMaxInsulin.toByteArray(2) + currentTDD.toInt().toByteArray(2) + 1.toByte() + basalProfile
+ }
+
+ override fun handleResponse(data: ByteArray): Boolean {
+ val success = super.handleResponse(data)
+ if (success) {
+ val medtrumTimeUtil = MedtrumTimeUtil()
+
+ val patchId = data.copyOfRange(RESP_PATCH_ID_START, RESP_PATCH_ID_END).toLong()
+ val time = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_TIME_START, RESP_TIME_END).toLong())
+ val basalType = enumValues()[data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()]
+ val basalValue = data.copyOfRange(RESP_BASAL_VALUE_START, RESP_BASAL_VALUE_END).toInt() * 0.05
+ val basalSequence = data.copyOfRange(RESP_BASAL_SEQUENCE_START, RESP_BASAL_SEQUENCE_END).toInt()
+ val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toLong()
+ val basalStartTime = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong())
+
+ medtrumPump.patchId = patchId
+ medtrumPump.lastTimeReceivedFromPump = time
+ medtrumPump.currentSequenceNumber = basalSequence // We are activated, set the new seq nr
+ medtrumPump.syncedSequenceNumber = basalSequence // We are activated, reset the synced seq nr ()
+
+ // Sync canula change
+ pumpSync.insertTherapyEventIfNewWithTimestamp(
+ timestamp = System.currentTimeMillis(),
+ type = DetailedBolusInfo.EventType.CANNULA_CHANGE,
+ pumpType = medtrumPump.pumpType(),
+ pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
+ )
+ pumpSync.insertTherapyEventIfNewWithTimestamp(
+ timestamp = System.currentTimeMillis(),
+ type = DetailedBolusInfo.EventType.INSULIN_CHANGE,
+ pumpType = medtrumPump.pumpType(),
+ pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
+ )
+
+ // Update the actual basal profile
+ medtrumPump.actualBasalProfile = basalProfile
+ medtrumPump.handleBasalStatusUpdate(basalType, basalValue, basalSequence, basalPatchId, basalStartTime, time)
+ }
+
+ return success
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/AuthorizePacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/AuthorizePacket.kt
new file mode 100644
index 0000000000..fce1d8c869
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/AuthorizePacket.kt
@@ -0,0 +1,57 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumPump
+import info.nightscout.pump.medtrum.comm.enums.CommandType.AUTH_REQ
+import info.nightscout.pump.medtrum.encryption.Crypt
+import info.nightscout.pump.medtrum.extension.toByteArray
+import info.nightscout.pump.medtrum.extension.toInt
+import info.nightscout.rx.logging.LTag
+import javax.inject.Inject
+
+class AuthorizePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
+
+ @Inject lateinit var medtrumPump: MedtrumPump
+
+ companion object {
+
+ private const val RESP_DEVICE_TYPE_START = 7
+ private const val RESP_DEVICE_TYPE_END = RESP_DEVICE_TYPE_START + 1
+ private const val RESP_VERSION_X_START = 8
+ private const val RESP_VERSION_X_END = RESP_VERSION_X_START + 1
+ private const val RESP_VERSION_Y_START = 9
+ private const val RESP_VERSION_Y_END = RESP_VERSION_Y_START + 1
+ private const val RESP_VERSION_Z_START = 10
+ private const val RESP_VERSION_Z_END = RESP_VERSION_Z_START + 1
+ }
+
+ init {
+ opCode = AUTH_REQ.code
+ expectedMinRespLength = RESP_VERSION_Z_END
+ }
+
+ override fun getRequest(): ByteArray {
+ val role = 2 // Fixed to 2 for pump
+ val key = Crypt().keyGen(medtrumPump.pumpSN)
+ return byteArrayOf(opCode) + byteArrayOf(role.toByte()) + medtrumPump.patchSessionToken.toByteArray(4) + key.toByteArray(4)
+ }
+
+ override fun handleResponse(data: ByteArray): Boolean {
+ val success = super.handleResponse(data)
+ if (success) {
+ val deviceType = data.copyOfRange(RESP_DEVICE_TYPE_START, RESP_DEVICE_TYPE_END).toInt()
+ val swVersion = "" + data.copyOfRange(RESP_VERSION_X_START, RESP_VERSION_X_END).toInt() + "." + data.copyOfRange(RESP_VERSION_Y_START, RESP_VERSION_Y_END).toInt() + "." + data.copyOfRange(
+ RESP_VERSION_Z_START, RESP_VERSION_Z_END
+ ).toInt()
+
+ if (medtrumPump.deviceType != deviceType) {
+ medtrumPump.deviceType = deviceType
+ }
+ if (medtrumPump.swVersion != swVersion) {
+ medtrumPump.swVersion = swVersion
+ }
+ aapsLogger.debug(LTag.PUMPCOMM, "GetDeviceTypeState: deviceType: ${deviceType}, swVersion: ${swVersion}")
+ }
+ return success
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/CancelBolusPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/CancelBolusPacket.kt
new file mode 100644
index 0000000000..3a2e2cbcca
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/CancelBolusPacket.kt
@@ -0,0 +1,20 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.comm.enums.CommandType.CANCEL_BOLUS
+
+class CancelBolusPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
+
+ init {
+ opCode = CANCEL_BOLUS.code
+ }
+
+ override fun getRequest(): ByteArray {
+ // Bolus types:
+ // 1 = normal
+ // 2 = Extended
+ // 3 = Combi
+ val bolusType: Byte = 1 // Only support for normal bolus for now
+ return byteArrayOf(opCode) + bolusType
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/CancelTempBasalPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/CancelTempBasalPacket.kt
new file mode 100644
index 0000000000..f6b54aaa18
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/CancelTempBasalPacket.kt
@@ -0,0 +1,51 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumPump
+import info.nightscout.pump.medtrum.comm.enums.CommandType.CANCEL_TEMP_BASAL
+import info.nightscout.pump.medtrum.comm.enums.BasalType
+import info.nightscout.pump.medtrum.extension.toInt
+import info.nightscout.pump.medtrum.extension.toLong
+import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
+import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.utils.DateUtil
+import javax.inject.Inject
+
+class CancelTempBasalPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
+
+ @Inject lateinit var medtrumPump: MedtrumPump
+ @Inject lateinit var dateUtil: DateUtil
+
+ companion object {
+
+ private const val RESP_BASAL_TYPE_START = 6
+ private const val RESP_BASAL_TYPE_END = RESP_BASAL_TYPE_START + 1
+ private const val RESP_BASAL_RATE_START = RESP_BASAL_TYPE_END
+ private const val RESP_BASAL_RATE_END = RESP_BASAL_RATE_START + 2
+ private const val RESP_BASAL_SEQUENCE_START = RESP_BASAL_RATE_END
+ private const val RESP_BASAL_SEQUENCE_END = RESP_BASAL_SEQUENCE_START + 2
+ private const val RESP_BASAL_PATCH_ID_START = RESP_BASAL_SEQUENCE_END
+ private const val RESP_BASAL_PATCH_ID_END = RESP_BASAL_PATCH_ID_START + 2
+ private const val RESP_BASAL_START_TIME_START = RESP_BASAL_PATCH_ID_END
+ private const val RESP_BASAL_START_TIME_END = RESP_BASAL_START_TIME_START + 4
+ }
+
+ init {
+ opCode = CANCEL_TEMP_BASAL.code
+ expectedMinRespLength = RESP_BASAL_START_TIME_END
+ }
+
+ override fun handleResponse(data: ByteArray): Boolean {
+ val success = super.handleResponse(data)
+ if (success) {
+ val basalType = enumValues()[data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()]
+ val basalRate = data.copyOfRange(RESP_BASAL_RATE_START, RESP_BASAL_RATE_END).toInt() * 0.05
+ val basalSequence = data.copyOfRange(RESP_BASAL_SEQUENCE_START, RESP_BASAL_SEQUENCE_END).toInt()
+ val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toLong()
+ val basalStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong())
+
+ medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalStartTime)
+ }
+ return success
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/ClearPumpAlarmPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/ClearPumpAlarmPacket.kt
new file mode 100644
index 0000000000..15f860fc50
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/ClearPumpAlarmPacket.kt
@@ -0,0 +1,16 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.comm.enums.CommandType.CLEAR_ALARM
+import info.nightscout.pump.medtrum.extension.toByteArray
+
+class ClearPumpAlarmPacket(injector: HasAndroidInjector, val clearType: Int) : MedtrumPacket(injector) {
+
+ init {
+ opCode = CLEAR_ALARM.code
+ }
+
+ override fun getRequest(): ByteArray {
+ return byteArrayOf(opCode) + clearType.toByteArray(2)
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/GetDeviceTypePacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/GetDeviceTypePacket.kt
new file mode 100644
index 0000000000..22de4d50c3
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/GetDeviceTypePacket.kt
@@ -0,0 +1,35 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.comm.enums.CommandType.GET_DEVICE_TYPE
+import info.nightscout.pump.medtrum.extension.toInt
+import info.nightscout.pump.medtrum.extension.toLong
+
+class GetDeviceTypePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
+
+ var deviceType: Int = 0
+ var deviceSN: Long = 0
+
+ companion object {
+
+ private const val RESP_DEVICE_TYPE_START = 6
+ private const val RESP_DEVICE_TYPE_END = RESP_DEVICE_TYPE_START + 1
+ private const val RESP_DEVICE_SN_START = 7
+ private const val RESP_DEVICE_SN_END = RESP_DEVICE_SN_START + 4
+ }
+
+ init {
+ opCode = GET_DEVICE_TYPE.code
+ expectedMinRespLength = RESP_DEVICE_SN_END
+ }
+
+ override fun handleResponse(data: ByteArray): Boolean {
+ val success = super.handleResponse(data)
+ if (success) {
+ deviceType = data.copyOfRange(RESP_DEVICE_TYPE_START, RESP_DEVICE_TYPE_END).toInt()
+ deviceSN = data.copyOfRange(RESP_DEVICE_SN_START, RESP_DEVICE_SN_END).toLong()
+ }
+
+ return success
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/GetRecordPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/GetRecordPacket.kt
new file mode 100644
index 0000000000..ad127fed14
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/GetRecordPacket.kt
@@ -0,0 +1,353 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.interfaces.pump.DetailedBolusInfoStorage
+import info.nightscout.interfaces.pump.PumpSync
+import info.nightscout.interfaces.pump.TemporaryBasalStorage
+import info.nightscout.pump.medtrum.MedtrumPump
+import info.nightscout.pump.medtrum.comm.enums.CommandType.GET_RECORD
+import info.nightscout.pump.medtrum.comm.enums.BasalType
+import info.nightscout.pump.medtrum.comm.enums.BolusType
+import info.nightscout.pump.medtrum.extension.toByteArray
+import info.nightscout.pump.medtrum.extension.toFloat
+import info.nightscout.pump.medtrum.extension.toInt
+import info.nightscout.pump.medtrum.extension.toLong
+import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
+import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.utils.DateUtil
+import info.nightscout.shared.utils.T
+import javax.inject.Inject
+
+class GetRecordPacket(injector: HasAndroidInjector, private val recordIndex: Int) : MedtrumPacket(injector) {
+
+ @Inject lateinit var medtrumPump: MedtrumPump
+ @Inject lateinit var pumpSync: PumpSync
+ @Inject lateinit var temporaryBasalStorage: TemporaryBasalStorage
+ @Inject lateinit var detailedBolusInfoStorage: DetailedBolusInfoStorage
+ @Inject lateinit var dateUtil: DateUtil
+
+ companion object {
+
+ private const val RESP_RECORD_HEADER_START = 6
+ private const val RESP_RECORD_HEADER_END = RESP_RECORD_HEADER_START + 1
+ private const val RESP_RECORD_UNKNOWN_START = RESP_RECORD_HEADER_END
+ private const val RESP_RECORD_UNKNOWN_END = RESP_RECORD_UNKNOWN_START + 1
+ private const val RESP_RECORD_TYPE_START = RESP_RECORD_UNKNOWN_END
+ private const val RESP_RECORD_TYPE_END = RESP_RECORD_TYPE_START + 1
+ private const val RESP_RECORD_UNKNOWN1_START = RESP_RECORD_TYPE_END
+ private const val RESP_RECORD_UNKNOWN1_END = RESP_RECORD_UNKNOWN1_START + 1
+ private const val RESP_RECORD_SERIAL_START = RESP_RECORD_UNKNOWN1_END
+ private const val RESP_RECORD_SERIAL_END = RESP_RECORD_SERIAL_START + 4
+ private const val RESP_RECORD_PATCHID_START = RESP_RECORD_SERIAL_END
+ private const val RESP_RECORD_PATCHID_END = RESP_RECORD_PATCHID_START + 2
+ private const val RESP_RECORD_SEQUENCE_START = RESP_RECORD_PATCHID_END
+ private const val RESP_RECORD_SEQUENCE_END = RESP_RECORD_SEQUENCE_START + 2
+ private const val RESP_RECORD_DATA_START = RESP_RECORD_SEQUENCE_END
+
+ private const val VALID_HEADER = 170
+ private const val BOLUS_RECORD = 1
+ private const val BOLUS_RECORD_ALT = 65
+ private const val BASAL_RECORD = 2
+ private const val BASAL_RECORD_ALT = 66
+ private const val ALARM_RECORD = 3
+ private const val AUTO_RECORD = 4
+ private const val TIME_SYNC_RECORD = 5
+ private const val AUTO1_RECORD = 6
+ private const val AUTO2_RECORD = 7
+ private const val AUTO3_RECORD = 8
+ private const val TDD_RECORD = 9
+
+ }
+
+ init {
+ opCode = GET_RECORD.code
+ expectedMinRespLength = RESP_RECORD_DATA_START
+ }
+
+ override fun getRequest(): ByteArray {
+ return byteArrayOf(opCode) + recordIndex.toByteArray(2) + medtrumPump.patchId.toByteArray(2)
+ }
+
+ override fun handleResponse(data: ByteArray): Boolean {
+ val success = super.handleResponse(data)
+ if (success) {
+ val recordHeader = data.copyOfRange(RESP_RECORD_HEADER_START, RESP_RECORD_HEADER_END).toInt()
+ val recordUnknown = data.copyOfRange(RESP_RECORD_UNKNOWN_START, RESP_RECORD_UNKNOWN_END).toInt()
+ val recordType = data.copyOfRange(RESP_RECORD_TYPE_START, RESP_RECORD_TYPE_END).toInt()
+ val recordSerial = data.copyOfRange(RESP_RECORD_SERIAL_START, RESP_RECORD_SERIAL_END).toLong()
+ val recordPatchId = data.copyOfRange(RESP_RECORD_PATCHID_START, RESP_RECORD_PATCHID_END).toInt()
+ val recordSequence = data.copyOfRange(RESP_RECORD_SEQUENCE_START, RESP_RECORD_SEQUENCE_END).toInt()
+
+ aapsLogger.debug(
+ LTag.PUMPCOMM,
+ "GetRecordPacket HandleResponse: Record header: $recordHeader, unknown: $recordUnknown, type: $recordType, serial: $recordSerial, patchId: $recordPatchId, " + "sequence: $recordSequence"
+ )
+
+ medtrumPump.syncedSequenceNumber = recordSequence // Assume sync upwards
+
+ if (recordHeader == VALID_HEADER) {
+ when (recordType) {
+ BOLUS_RECORD, BOLUS_RECORD_ALT -> {
+ aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BOLUS_RECORD")
+ val typeAndWizard = data.copyOfRange(RESP_RECORD_DATA_START, RESP_RECORD_DATA_START + 1).toInt()
+ val bolusCause = data.copyOfRange(RESP_RECORD_DATA_START + 1, RESP_RECORD_DATA_START + 2).toInt()
+ val unknown = data.copyOfRange(RESP_RECORD_DATA_START + 2, RESP_RECORD_DATA_START + 4).toInt()
+ val bolusStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_RECORD_DATA_START + 4, RESP_RECORD_DATA_START + 8).toLong())
+ val bolusNormalAmount = data.copyOfRange(RESP_RECORD_DATA_START + 8, RESP_RECORD_DATA_START + 10).toInt() * 0.05
+ val bolusNormalDelivered = data.copyOfRange(RESP_RECORD_DATA_START + 10, RESP_RECORD_DATA_START + 12).toInt() * 0.05
+ val bolusExtendedAmount = data.copyOfRange(RESP_RECORD_DATA_START + 12, RESP_RECORD_DATA_START + 14).toInt() * 0.05
+ val bolusExtendedDuration = data.copyOfRange(RESP_RECORD_DATA_START + 14, RESP_RECORD_DATA_START + 16).toLong() * 1000
+ val bolusExtendedDelivered = data.copyOfRange(RESP_RECORD_DATA_START + 16, RESP_RECORD_DATA_START + 18).toInt() * 0.05
+ val bolusCarb = data.copyOfRange(RESP_RECORD_DATA_START + 18, RESP_RECORD_DATA_START + 20).toInt()
+ val bolusGlucose = data.copyOfRange(RESP_RECORD_DATA_START + 20, RESP_RECORD_DATA_START + 22).toInt()
+ val bolusIOB = data.copyOfRange(RESP_RECORD_DATA_START + 22, RESP_RECORD_DATA_START + 24).toInt()
+ val unkown1 = data.copyOfRange(RESP_RECORD_DATA_START + 24, RESP_RECORD_DATA_START + 26).toInt()
+ val unkown2 = data.copyOfRange(RESP_RECORD_DATA_START + 26, RESP_RECORD_DATA_START + 28).toInt()
+ val bolusType = enumValues()[typeAndWizard and 0x0F]
+ val bolusWizard = (typeAndWizard and 0xF0) != 0
+ aapsLogger.debug(
+ LTag.PUMPCOMM,
+ "GetRecordPacket HandleResponse: BOLUS_RECORD: typeAndWizard: $typeAndWizard, bolusCause: $bolusCause, unknown: $unknown, bolusStartTime: $bolusStartTime, " + "bolusNormalAmount: $bolusNormalAmount, bolusNormalDelivered: $bolusNormalDelivered, bolusExtendedAmount: $bolusExtendedAmount, bolusExtendedDuration: $bolusExtendedDuration, " + "bolusExtendedDelivered: $bolusExtendedDelivered, bolusCarb: $bolusCarb, bolusGlucose: $bolusGlucose, bolusIOB: $bolusIOB, unkown1: $unkown1, unkown2: $unkown2, " + "bolusType: $bolusType, bolusWizard: $bolusWizard"
+ )
+
+ if (bolusType == BolusType.NORMAL) {
+ val detailedBolusInfo = detailedBolusInfoStorage.findDetailedBolusInfo(bolusStartTime, bolusNormalDelivered)
+ var newRecord = false
+ if (detailedBolusInfo != null) {
+ val syncOk = pumpSync.syncBolusWithTempId(
+ timestamp = bolusStartTime,
+ amount = bolusNormalDelivered,
+ temporaryId = detailedBolusInfo.timestamp,
+ type = detailedBolusInfo.bolusType,
+ pumpId = bolusStartTime,
+ pumpType = medtrumPump.pumpType(),
+ pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
+ )
+ if (syncOk == false) {
+ aapsLogger.warn(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BOLUS_RECORD: Failed to sync bolus with tempId: ${detailedBolusInfo.timestamp}")
+ // detailedInfo can be from another similar record. Reinsert
+ detailedBolusInfoStorage.add(detailedBolusInfo)
+ }
+ } else {
+ newRecord = pumpSync.syncBolusWithPumpId(
+ timestamp = bolusStartTime,
+ amount = bolusNormalDelivered,
+ type = null,
+ pumpId = bolusStartTime,
+ pumpType = medtrumPump.pumpType(),
+ pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
+ )
+ }
+
+ aapsLogger.debug(
+ LTag.PUMPCOMM,
+ "from record: ${if (newRecord) "**NEW** " else ""}EVENT BOLUS ${dateUtil.dateAndTimeString(bolusStartTime)} ($bolusStartTime) Bolus: ${bolusNormalDelivered}U "
+ )
+ if (bolusStartTime > medtrumPump.lastBolusTime) {
+ medtrumPump.lastBolusTime = bolusStartTime
+ medtrumPump.lastBolusAmount = bolusNormalDelivered
+ }
+ } else if (bolusType == BolusType.EXTENDED) {
+ val newRecord = pumpSync.syncExtendedBolusWithPumpId(
+ timestamp = bolusStartTime,
+ amount = bolusExtendedDelivered,
+ duration = bolusExtendedDuration,
+ isEmulatingTB = false,
+ pumpId = bolusStartTime,
+ pumpType = medtrumPump.pumpType(),
+ pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
+ )
+ aapsLogger.debug(
+ LTag.PUMPCOMM,
+ "from record: ${if (newRecord) "**NEW** " else ""}EVENT EXTENDED BOLUS ${dateUtil.dateAndTimeString(bolusStartTime)} ($bolusStartTime) Bolus: ${bolusNormalDelivered}U "
+ )
+ } else if (bolusType == BolusType.COMBI) {
+ // Note, this should never happen, as we don't use combo bolus
+ val detailedBolusInfo = detailedBolusInfoStorage.findDetailedBolusInfo(bolusStartTime, bolusNormalDelivered)
+ val newRecord = pumpSync.syncBolusWithPumpId(
+ timestamp = bolusStartTime,
+ amount = bolusNormalDelivered,
+ type = detailedBolusInfo?.bolusType,
+ pumpId = bolusStartTime,
+ pumpType = medtrumPump.pumpType(),
+ pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
+ )
+ pumpSync.syncExtendedBolusWithPumpId(
+ timestamp = bolusStartTime,
+ amount = bolusExtendedDelivered,
+ duration = bolusExtendedDuration,
+ isEmulatingTB = false,
+ pumpId = bolusStartTime,
+ pumpType = medtrumPump.pumpType(),
+ pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
+ )
+ aapsLogger.error(
+ LTag.PUMPCOMM,
+ "from record: ${if (newRecord) "**NEW** " else ""}EVENT COMBI BOLUS ${dateUtil.dateAndTimeString(bolusStartTime)} ($bolusStartTime) Bolus: ${bolusNormalDelivered}U Extended: ${bolusExtendedDelivered} THIS SHOULD NOT HAPPEN!!!"
+ )
+ if (!newRecord && detailedBolusInfo != null) {
+ // detailedInfo can be from another similar record. Reinsert
+ detailedBolusInfoStorage.add(detailedBolusInfo)
+ }
+ if (bolusStartTime > medtrumPump.lastBolusTime) {
+ medtrumPump.lastBolusTime = bolusStartTime
+ medtrumPump.lastBolusAmount = bolusNormalDelivered
+ }
+ } else {
+ aapsLogger.error(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BOLUS_RECORD: Unknown bolus type: $bolusType")
+ }
+
+ }
+
+ BASAL_RECORD, BASAL_RECORD_ALT -> {
+ val medtrumTimeUtil = MedtrumTimeUtil()
+ val basalStartTime = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_RECORD_DATA_START, RESP_RECORD_DATA_START + 4).toLong())
+ val basalEndTime = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_RECORD_DATA_START + 4, RESP_RECORD_DATA_START + 8).toLong())
+ val basalType = enumValues()[data.copyOfRange(RESP_RECORD_DATA_START + 8, RESP_RECORD_DATA_START + 9).toInt()]
+ val basalEndReason = data.copyOfRange(RESP_RECORD_DATA_START + 9, RESP_RECORD_DATA_START + 10).toInt()
+ val basalRate = data.copyOfRange(RESP_RECORD_DATA_START + 10, RESP_RECORD_DATA_START + 12).toInt() * 0.05
+ val basalDelivered = data.copyOfRange(RESP_RECORD_DATA_START + 12, RESP_RECORD_DATA_START + 14).toInt() * 0.05
+ val basalPercent = data.copyOfRange(RESP_RECORD_DATA_START + 14, RESP_RECORD_DATA_START + 16).toInt()
+
+ aapsLogger.debug(
+ LTag.PUMPCOMM,
+ "GetRecordPacket HandleResponse: BASAL_RECORD: Start: $basalStartTime, End: $basalEndTime, Type: $basalType, EndReason: $basalEndReason, Rate: $basalRate, Delivered: $basalDelivered, Percent: $basalPercent"
+ )
+
+ when (basalType) {
+ BasalType.STANDARD -> {
+ aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BASAL_RECORD: Standard basal")
+ // If we are here it means the basal has ended
+ }
+
+ BasalType.ABSOLUTE_TEMP, BasalType.RELATIVE_TEMP -> {
+ aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BASAL_RECORD: Absolute temp basal")
+ var duration = (basalEndTime - basalStartTime)
+ // Work around for pumpSync not accepting 0 duration.
+ // sometimes we get 0 duration for very short basal because the pump only reports time in seconds
+ if (duration < 250) duration = 250 // 250ms to make sure AAPS accepts it
+
+ val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
+ timestamp = basalStartTime,
+ rate = if (basalType == BasalType.ABSOLUTE_TEMP) basalRate else basalPercent.toDouble(),
+ duration = duration,
+ isAbsolute = (basalType == BasalType.ABSOLUTE_TEMP),
+ type = PumpSync.TemporaryBasalType.NORMAL,
+ pumpId = basalStartTime,
+ pumpType = medtrumPump.pumpType(),
+ pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
+ )
+ aapsLogger.debug(
+ LTag.PUMPCOMM,
+ "handleBasalStatusUpdate from record: ${if (newRecord) "**NEW** " else ""}EVENT TEMP_SYNC: ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) " +
+ "Rate: $basalRate Duration: ${duration}"
+ )
+ }
+
+ in BasalType.SUSPEND_LOW_GLUCOSE..BasalType.STOP -> {
+ aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BASAL_RECORD: Suspend basal")
+ val duration = (basalEndTime - basalStartTime)
+ val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
+ timestamp = basalEndTime,
+ rate = 0.0,
+ duration = duration,
+ isAbsolute = true,
+ type = PumpSync.TemporaryBasalType.PUMP_SUSPEND,
+ pumpId = basalStartTime,
+ pumpType = medtrumPump.pumpType(),
+ pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
+ )
+ aapsLogger.debug(
+ LTag.PUMPCOMM,
+ "handleBasalStatusUpdate from record: ${if (newRecord) "**NEW** " else ""}EVENT SUSPEND: ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) " +
+ "Rate: $basalRate Duration: ${duration}"
+ )
+ }
+
+ else -> {
+ aapsLogger.error(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BASAL_RECORD: Unknown basal type: $basalType")
+ }
+ }
+ }
+
+ ALARM_RECORD -> {
+ aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: ALARM_RECORD")
+ }
+
+ AUTO_RECORD -> {
+ aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: AUTO_RECORD")
+ }
+
+ TIME_SYNC_RECORD -> {
+ aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: TIME_SYNC_RECORD")
+ }
+
+ AUTO1_RECORD -> {
+ aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: AUTO1_RECORD")
+ }
+
+ AUTO2_RECORD -> {
+ aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: AUTO2_RECORD")
+ }
+
+ AUTO3_RECORD -> {
+ aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: AUTO3_RECORD")
+ }
+
+ TDD_RECORD -> {
+ aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: TDD_RECORD")
+ val timestamp = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_RECORD_DATA_START, RESP_RECORD_DATA_START + 4).toLong())
+ val timeZoneOffset = data.copyOfRange(RESP_RECORD_DATA_START + 4, RESP_RECORD_DATA_START + 6).toInt()
+ val tddMins = data.copyOfRange(RESP_RECORD_DATA_START + 6, RESP_RECORD_DATA_START + 8).toInt()
+ val glucoseRecordTime = data.copyOfRange(RESP_RECORD_DATA_START + 8, RESP_RECORD_DATA_START + 12).toLong()
+ val tdd = data.copyOfRange(RESP_RECORD_DATA_START + 12, RESP_RECORD_DATA_START + 16).toFloat()
+ val basalTdd = data.copyOfRange(RESP_RECORD_DATA_START + 16, RESP_RECORD_DATA_START + 20).toFloat()
+ val glucose = data.copyOfRange(RESP_RECORD_DATA_START + 20, RESP_RECORD_DATA_START + 24).toFloat()
+ val unknown = data.copyOfRange(RESP_RECORD_DATA_START + 24, RESP_RECORD_DATA_START + 28).toFloat()
+ val meanSomething = data.copyOfRange(RESP_RECORD_DATA_START + 28, RESP_RECORD_DATA_START + 32).toFloat()
+ val usedTdd = data.copyOfRange(RESP_RECORD_DATA_START + 32, RESP_RECORD_DATA_START + 36).toFloat()
+ val usedIBasal = data.copyOfRange(RESP_RECORD_DATA_START + 36, RESP_RECORD_DATA_START + 40).toFloat()
+ val usedSgBasal = data.copyOfRange(RESP_RECORD_DATA_START + 40, RESP_RECORD_DATA_START + 44).toFloat()
+ val usedUMax = data.copyOfRange(RESP_RECORD_DATA_START + 44, RESP_RECORD_DATA_START + 48).toFloat()
+ val newTdd = data.copyOfRange(RESP_RECORD_DATA_START + 48, RESP_RECORD_DATA_START + 52).toFloat()
+ val newIBasal = data.copyOfRange(RESP_RECORD_DATA_START + 52, RESP_RECORD_DATA_START + 56).toFloat()
+ val newSgBasal = data.copyOfRange(RESP_RECORD_DATA_START + 56, RESP_RECORD_DATA_START + 60).toFloat()
+ val newUMax = data.copyOfRange(RESP_RECORD_DATA_START + 60, RESP_RECORD_DATA_START + 64).toFloat()
+
+ aapsLogger.debug(
+ LTag.PUMPCOMM, "TDD_RECORD: timestamp: $timestamp, timeZoneOffset: $timeZoneOffset, tddMins: $tddMins, glucoseRecordTime: $glucoseRecordTime, tdd: " +
+ "$tdd, basalTdd: $basalTdd, glucose: $glucose, unknown: $unknown, meanSomething: $meanSomething, usedTdd: $usedTdd, usedIBasal: $usedIBasal, usedSgBasal: " +
+ "$usedSgBasal, usedUMax: $usedUMax, newTdd: $newTdd, newIBasal: $newIBasal, newSgBasal: $newSgBasal, newUMax: $newUMax"
+ )
+
+ val newRecord = pumpSync.createOrUpdateTotalDailyDose(
+ timestamp = timestamp,
+ bolusAmount = (tdd - basalTdd).toDouble(),
+ basalAmount = basalTdd.toDouble(),
+ totalAmount = tdd.toDouble(),
+ pumpId = timestamp,
+ pumpType = medtrumPump.pumpType(),
+ pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
+ )
+
+ aapsLogger.debug(
+ LTag.PUMPCOMM,
+ "handleBasalStatusUpdate from record: ${if (newRecord) "**NEW** " else ""}EVENT TDD: ${dateUtil.dateAndTimeString(timestamp)} ($timestamp) " +
+ "TDD: $tdd, BasalTDD: $basalTdd, BolusTDD: ${tdd - basalTdd}"
+ )
+
+ }
+
+ else -> {
+ aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: Unknown record type: $recordType")
+ }
+ }
+
+ } else {
+ aapsLogger.error(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: Invalid record header")
+ }
+ }
+
+ return success
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/GetTimePacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/GetTimePacket.kt
new file mode 100644
index 0000000000..45efb5c30b
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/GetTimePacket.kt
@@ -0,0 +1,34 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumPump
+import info.nightscout.pump.medtrum.comm.enums.CommandType.GET_TIME
+import info.nightscout.pump.medtrum.extension.toLong
+import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
+import javax.inject.Inject
+
+class GetTimePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
+
+ @Inject lateinit var medtrumPump: MedtrumPump
+
+ companion object {
+
+ private const val RESP_TIME_START = 6
+ private const val RESP_TIME_END = RESP_TIME_START + 4
+ }
+
+ init {
+ opCode = GET_TIME.code
+ expectedMinRespLength = RESP_TIME_END
+ }
+
+ override fun handleResponse(data: ByteArray): Boolean {
+ val success = super.handleResponse(data)
+ if (success) {
+ val time = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_TIME_START, RESP_TIME_END).toLong())
+ medtrumPump.lastTimeReceivedFromPump = time
+ }
+
+ return success
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/MedtrumPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/MedtrumPacket.kt
new file mode 100644
index 0000000000..6b175c06df
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/MedtrumPacket.kt
@@ -0,0 +1,79 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.extension.toInt
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import javax.inject.Inject
+
+open class MedtrumPacket(protected var injector: HasAndroidInjector) {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+
+ var opCode: Byte = 0
+ var failed = false
+ var expectedMinRespLength = RESP_RESULT_END
+
+ companion object {
+
+ const val RESP_OPCODE_START = 1
+ const val RESP_OPCODE_END = RESP_OPCODE_START + 1
+ const val RESP_RESULT_START = 4
+ const val RESP_RESULT_END = RESP_RESULT_START + 2
+
+ private const val RESP_WAITING = 16384
+ }
+
+ init {
+ // @Suppress("LeakingThis")
+ injector.androidInjector().inject(this)
+ }
+
+ open fun getRequest(): ByteArray {
+ return byteArrayOf(opCode)
+ }
+
+ /** handles a response from the Medtrum pump, returns true if command was successfull, returns false if command failed or waiting for response */
+ open fun handleResponse(data: ByteArray): Boolean {
+ // Check for broken packets
+ if (RESP_RESULT_END > data.size) {
+ failed = true
+ aapsLogger.debug(LTag.PUMPCOMM, "handleResponse: Unexpected response length, expected: $expectedMinRespLength got: ${data.size}")
+ return false
+ }
+
+ val incomingOpCode: Byte = data.copyOfRange(RESP_OPCODE_START, RESP_OPCODE_END).first()
+ val responseCode = data.copyOfRange(RESP_RESULT_START, RESP_RESULT_END).toInt()
+
+ return when {
+ incomingOpCode != opCode -> {
+ failed = true
+ aapsLogger.error(LTag.PUMPCOMM, "handleResponse: Unexpected command, expected: $opCode got: $incomingOpCode")
+ false
+ }
+
+ responseCode == 0 -> {
+ // Check if length is what is expected from this type of packet
+ if (expectedMinRespLength > data.size) {
+ failed = true
+ aapsLogger.debug(LTag.PUMPCOMM, "handleResponse: Unexpected response length, expected: $expectedMinRespLength got: ${data.size}")
+ return false
+ }
+ aapsLogger.debug(LTag.PUMPCOMM, "handleResponse: Happy command: $opCode response: $responseCode")
+ true
+ }
+
+ responseCode == RESP_WAITING -> {
+ aapsLogger.debug(LTag.PUMPCOMM, "handleResponse: Waiting command: $opCode response: $responseCode")
+ // Waiting do nothing
+ false
+ }
+
+ else -> {
+ failed = true
+ aapsLogger.warn(LTag.PUMPCOMM, "handleResponse: Error in command: $opCode response: $responseCode")
+ false
+ }
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt
new file mode 100644
index 0000000000..a37b5323cd
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt
@@ -0,0 +1,236 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumPump
+import info.nightscout.pump.medtrum.comm.enums.AlarmState
+import info.nightscout.pump.medtrum.comm.enums.BasalType
+import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
+import info.nightscout.pump.medtrum.extension.toInt
+import info.nightscout.pump.medtrum.extension.toLong
+import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import javax.inject.Inject
+
+class NotificationPacket(val injector: HasAndroidInjector) {
+
+ /**
+ * This is a bit of a special packet, as it is not a command packet
+ * but a notification packet. It is sent by the pump to the phone
+ * when the pump has a notification to send.
+ *
+ * Notifications are sent regualary, regardless of the pump state.
+ *
+ * There can be multiple messages in one packet, this is noted by the fieldMask.
+ *
+ * Byte 1: State (Handle a state change directly? before analyzing further?)
+ * Byte 2-3: FieldMask (BitMask which tells the fields present in the message)
+ * Byte 4-end : status data
+ *
+ * When multiple fields are in the message, the data is concatenated.
+ * This kind of message can also come as a response of SynchronizePacket,
+ * and can be handled here by handleMaskedMessage() as well.
+ */
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var medtrumPump: MedtrumPump
+
+ companion object {
+
+ private const val NOTIF_STATE_START = 0
+ private const val NOTIF_STATE_END = NOTIF_STATE_START + 1
+
+ private const val MASK_SUSPEND = 0x01
+ private const val MASK_NORMAL_BOLUS = 0x02
+ private const val MASK_EXTENDED_BOLUS = 0x04
+ private const val MASK_BASAL = 0x08
+
+ private const val MASK_SETUP = 0x10
+ private const val MASK_RESERVOIR = 0x20
+ private const val MASK_START_TIME = 0x40
+ private const val MASK_BATTERY = 0x80
+
+ private const val MASK_STORAGE = 0x100
+ private const val MASK_ALARM = 0x200
+ private const val MASK_AGE = 0x400
+ private const val MASK_UNKNOWN_1 = 0x800
+
+ private const val MASK_UNUSED_CGM = 0x1000
+ private const val MASK_UNUSED_COMMAND_CONFIRM = 0x2000
+ private const val MASK_UNUSED_AUTO_STATUS = 0x4000
+ private const val MASK_UNUSED_LEGACY = 0x8000
+ }
+
+ init {
+ injector.androidInjector().inject(this)
+ }
+
+ fun handleNotification(notification: ByteArray) {
+ val state = MedtrumPumpState.fromByte(notification[0])
+ aapsLogger.debug(LTag.PUMPCOMM, "Notification state: $state, current state: ${medtrumPump.pumpState}")
+
+ if (state != medtrumPump.pumpState) {
+ aapsLogger.debug(LTag.PUMPCOMM, "State changed from ${medtrumPump.pumpState} to $state")
+ medtrumPump.pumpState = state
+ }
+
+ if (notification.size > NOTIF_STATE_END) {
+ handleMaskedMessage(notification.copyOfRange(NOTIF_STATE_END, notification.size))
+ }
+ }
+
+ /**
+ * Handle a message with a field mask, can be used by other packets as well
+ */
+ fun handleMaskedMessage(data: ByteArray) {
+ val fieldMask = data.copyOfRange(0, 2).toInt()
+ var offset = 2
+
+ aapsLogger.debug(LTag.PUMPCOMM, "Message field mask: $fieldMask")
+
+ if (fieldMask and MASK_SUSPEND != 0) {
+ aapsLogger.debug(LTag.PUMPCOMM, "Suspend notification received")
+ medtrumPump.suspendTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong())
+ aapsLogger.debug(LTag.PUMPCOMM, "Suspend time: ${medtrumPump.suspendTime}")
+ offset += 4
+ }
+
+ if (fieldMask and MASK_NORMAL_BOLUS != 0) {
+ aapsLogger.debug(LTag.PUMPCOMM, "Normal bolus notification received")
+ var bolusData = data.copyOfRange(offset, offset + 1).toInt()
+ var bolusType = bolusData and 0x7F
+ val bolusCompleted: Boolean = ((bolusData shr 7) and 0x01) != 0
+ var bolusDelivered = data.copyOfRange(offset + 1, offset + 3).toInt() * 0.05
+ aapsLogger.debug(LTag.PUMPCOMM, "Bolus type: $bolusType, bolusData: $bolusData bolus completed: $bolusCompleted, bolus delivered: $bolusDelivered")
+ medtrumPump.handleBolusStatusUpdate(bolusType, bolusCompleted, bolusDelivered)
+ offset += 3
+ }
+
+ if (fieldMask and MASK_EXTENDED_BOLUS != 0) {
+ aapsLogger.error(LTag.PUMPCOMM, "Extended bolus notification received, extended bolus not supported!")
+ // TODO Handle error and stop pump if this happens?
+ offset += 3
+ }
+
+ if (fieldMask and MASK_BASAL != 0) {
+ aapsLogger.debug(LTag.PUMPCOMM, "Basal notification received")
+ val basalType = enumValues()[data.copyOfRange(offset, offset + 1).toInt()]
+ var basalSequence = data.copyOfRange(offset + 1, offset + 3).toInt()
+ var basalPatchId = data.copyOfRange(offset + 3, offset + 5).toLong()
+ var basalStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset + 5, offset + 9).toLong())
+ var basalRateAndDelivery = data.copyOfRange(offset + 9, offset + 12).toInt()
+ var basalRate = (basalRateAndDelivery and 0xFFF) * 0.05
+ var basalDelivery = (basalRateAndDelivery shr 12) * 0.05
+ aapsLogger.debug(
+ LTag.PUMPCOMM,
+ "Basal type: $basalType, basal sequence: $basalSequence, basal patch id: $basalPatchId, basal time: $basalStartTime, basal rate: $basalRate, basal delivery: $basalDelivery"
+ )
+ // Don't spam with basal updates here, only if the running basal rate has changed, or a new basal is set
+ if (medtrumPump.lastBasalRate != basalRate || medtrumPump.lastBasalStartTime != basalStartTime) {
+ medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalStartTime)
+ }
+ offset += 12
+ }
+
+ if (fieldMask and MASK_SETUP != 0) {
+ aapsLogger.debug(LTag.PUMPCOMM, "Setup notification received")
+ medtrumPump.primeProgress = data.copyOfRange(offset, offset + 1).toInt()
+ aapsLogger.debug(LTag.PUMPCOMM, "Prime progress: ${medtrumPump.primeProgress}")
+ offset += 1
+ }
+
+ if (fieldMask and MASK_RESERVOIR != 0) {
+ aapsLogger.debug(LTag.PUMPCOMM, "Reservoir notification received")
+ medtrumPump.reservoir = data.copyOfRange(offset, offset + 2).toInt() * 0.05
+ aapsLogger.debug(LTag.PUMPCOMM, "Reservoir: ${medtrumPump.reservoir}")
+ offset += 2
+ }
+
+ if (fieldMask and MASK_START_TIME != 0) {
+ aapsLogger.debug(LTag.PUMPCOMM, "Start time notification received")
+ val patchStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong())
+ if (medtrumPump.patchStartTime != patchStartTime) {
+ aapsLogger.debug(LTag.PUMPCOMM, "Patch start time changed from ${medtrumPump.patchStartTime} to $patchStartTime")
+ medtrumPump.patchStartTime = patchStartTime
+ }
+ aapsLogger.debug(LTag.PUMPCOMM, "Patch start time: ${patchStartTime}")
+ offset += 4
+ }
+
+ if (fieldMask and MASK_BATTERY != 0) {
+ aapsLogger.debug(LTag.PUMPCOMM, "Battery notification received")
+ var parameter = data.copyOfRange(offset, offset + 3).toInt()
+ // Precision for voltage A is a guess, voltage B is the important one, threshold: < 2.64
+ medtrumPump.batteryVoltage_A = (parameter and 0xFFF) / 512.0
+ medtrumPump.batteryVoltage_B = (parameter shr 12) / 512.0
+ aapsLogger.debug(LTag.PUMPCOMM, "Battery voltage A: ${medtrumPump.batteryVoltage_A}, battery voltage B: ${medtrumPump.batteryVoltage_B}")
+ offset += 3
+ }
+
+ if (fieldMask and MASK_STORAGE != 0) {
+ aapsLogger.debug(LTag.PUMPCOMM, "Storage notification received")
+ val sequence = data.copyOfRange(offset, offset + 2).toInt()
+ if (sequence > medtrumPump.currentSequenceNumber) {
+ medtrumPump.currentSequenceNumber = sequence
+ }
+ val patchId = data.copyOfRange(offset + 2, offset + 4).toLong()
+ if (patchId != medtrumPump.patchId) {
+ aapsLogger.warn(LTag.PUMPCOMM, "handleMaskedMessage: We got wrong patch id!")
+ }
+ aapsLogger.debug(LTag.PUMPCOMM, "Last known sequence number: ${medtrumPump.currentSequenceNumber}, patch id: ${patchId}")
+ offset += 4
+ }
+
+ if (fieldMask and MASK_ALARM != 0) {
+ val alarmFlags = data.copyOfRange(offset, offset + 2).toInt()
+ val alarmParameter = data.copyOfRange(offset + 2, offset + 4).toInt()
+ aapsLogger.debug(LTag.PUMPCOMM, "Alarm notification received, Alarm flags: $alarmFlags, alarm parameter: $alarmParameter")
+
+ // If no alarm, clear activeAlarm list
+ if (alarmFlags == 0 && medtrumPump.activeAlarms.isNotEmpty()) {
+ medtrumPump.clearAlarmState()
+ } else if (alarmFlags != 0) {
+ // Check each alarm bit
+ for (i in 0..3) { // Only the first 3 flags are interesting for us, the rest we will get from the pump state
+ val alarmState = AlarmState.values()[i]
+ if ((alarmFlags shr i) and 1 != 0) {
+ // If the alarm bit is set, add the corresponding alarm to activeAlarms
+ medtrumPump.addAlarm(alarmState)
+ } else if (medtrumPump.activeAlarms.contains(alarmState)) {
+ // If the alarm bit is not set, and the corresponding alarm is in activeAlarms, remove it
+ medtrumPump.removeAlarm(alarmState)
+ }
+ }
+ }
+ offset += 4
+ }
+
+ if (fieldMask and MASK_AGE != 0) {
+ aapsLogger.debug(LTag.PUMPCOMM, "Age notification received")
+ medtrumPump.patchAge = data.copyOfRange(offset, offset + 4).toLong()
+ aapsLogger.debug(LTag.PUMPCOMM, "Patch age: ${medtrumPump.patchAge}")
+ offset += 4
+ }
+
+ if (fieldMask and MASK_UNKNOWN_1 != 0) {
+ aapsLogger.debug(LTag.PUMPCOMM, "Unknown 1 notification received, not handled!")
+ }
+
+ if (fieldMask and MASK_UNUSED_CGM != 0) {
+ aapsLogger.debug(LTag.PUMPCOMM, "Unused CGM notification received, not handled!")
+ }
+
+ if (fieldMask and MASK_UNUSED_COMMAND_CONFIRM != 0) {
+ // This one is a warning, as this happens we need to know about it, and maybe implement
+ aapsLogger.warn(LTag.PUMPCOMM, "Unused command confirm notification received, not handled!")
+ }
+
+ if (fieldMask and MASK_UNUSED_AUTO_STATUS != 0) {
+ aapsLogger.debug(LTag.PUMPCOMM, "Unused auto status notification received, not handled!")
+ }
+
+ if (fieldMask and MASK_UNUSED_LEGACY != 0) {
+ aapsLogger.debug(LTag.PUMPCOMM, "Unused legacy notification received, not handled!")
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/PollPatchPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/PollPatchPacket.kt
new file mode 100644
index 0000000000..e7adaeea2f
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/PollPatchPacket.kt
@@ -0,0 +1,11 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.comm.enums.CommandType.POLL_PATCH
+
+class PollPatchPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
+
+ init {
+ opCode = POLL_PATCH.code
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/PrimePacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/PrimePacket.kt
new file mode 100644
index 0000000000..70e0c12500
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/PrimePacket.kt
@@ -0,0 +1,11 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.comm.enums.CommandType.PRIME
+
+class PrimePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
+
+ init {
+ opCode = PRIME.code
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/ReadBolusStatePacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/ReadBolusStatePacket.kt
new file mode 100644
index 0000000000..4672b1d185
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/ReadBolusStatePacket.kt
@@ -0,0 +1,31 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.comm.enums.CommandType.READ_BOLUS_STATE
+
+class ReadBolusStatePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
+ // UNUSED
+ // Bolus sync is currently done by getting the records and syncing then with AAPS pumpSync there
+
+ var bolusData: ByteArray = byteArrayOf()
+
+ companion object {
+
+ private const val RESP_BOLUS_DATA_START = 6
+ }
+
+ init {
+ opCode = READ_BOLUS_STATE.code
+ expectedMinRespLength = RESP_BOLUS_DATA_START + 1
+ }
+
+ override fun handleResponse(data: ByteArray): Boolean {
+ val success = super.handleResponse(data)
+ if (success) {
+ // UNUSED
+ bolusData = data.copyOfRange(RESP_BOLUS_DATA_START, data.size)
+ }
+
+ return success
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/ResumePumpPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/ResumePumpPacket.kt
new file mode 100644
index 0000000000..aa613b3656
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/ResumePumpPacket.kt
@@ -0,0 +1,16 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.comm.enums.CommandType.RESUME_PUMP
+import info.nightscout.pump.medtrum.extension.toByteArray
+
+class ResumePumpPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
+
+ init {
+ opCode = RESUME_PUMP.code
+ }
+
+ override fun getRequest(): ByteArray {
+ return byteArrayOf(opCode)
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetBasalProfilePacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetBasalProfilePacket.kt
new file mode 100644
index 0000000000..88684d2096
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetBasalProfilePacket.kt
@@ -0,0 +1,57 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumPump
+import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_BASAL_PROFILE
+import info.nightscout.pump.medtrum.comm.enums.BasalType
+import info.nightscout.pump.medtrum.extension.toInt
+import info.nightscout.pump.medtrum.extension.toLong
+import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
+import javax.inject.Inject
+
+class SetBasalProfilePacket(injector: HasAndroidInjector, private val basalProfile: ByteArray) : MedtrumPacket(injector) {
+
+ @Inject lateinit var medtrumPump: MedtrumPump
+
+ companion object {
+
+ private const val RESP_BASAL_TYPE_START = 6
+ private const val RESP_BASAL_TYPE_END = RESP_BASAL_TYPE_START + 1
+ private const val RESP_BASAL_VALUE_START = 7
+ private const val RESP_BASAL_VALUE_END = RESP_BASAL_VALUE_START + 2
+ private const val RESP_BASAL_SEQUENCE_START = 9
+ private const val RESP_BASAL_SEQUENCE_END = RESP_BASAL_SEQUENCE_START + 2
+ private const val RESP_BASAL_PATCH_ID_START = 11
+ private const val RESP_BASAL_PATCH_ID_END = RESP_BASAL_PATCH_ID_START + 2
+ private const val RESP_BASAL_START_TIME_START = 13
+ private const val RESP_BASAL_START_TIME_END = RESP_BASAL_START_TIME_START + 4
+ }
+
+ init {
+ opCode = SET_BASAL_PROFILE.code
+ expectedMinRespLength = RESP_BASAL_START_TIME_END
+
+ }
+
+ override fun getRequest(): ByteArray {
+ val basalType: Byte = 1 // Fixed to normal basal
+ return byteArrayOf(opCode) + basalType + basalProfile
+ }
+
+ override fun handleResponse(data: ByteArray): Boolean {
+ val success = super.handleResponse(data)
+ if (success) {
+ val medtrumTimeUtil = MedtrumTimeUtil()
+ val basalType = enumValues()[data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()]
+ val basalValue = data.copyOfRange(RESP_BASAL_VALUE_START, RESP_BASAL_VALUE_END).toInt() * 0.05
+ val basalSequence = data.copyOfRange(RESP_BASAL_SEQUENCE_START, RESP_BASAL_SEQUENCE_END).toInt()
+ val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toLong()
+ val basalStartTime = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong())
+
+ // Update the actual basal profile
+ medtrumPump.actualBasalProfile = basalProfile
+ medtrumPump.handleBasalStatusUpdate(basalType, basalValue, basalSequence, basalPatchId, basalStartTime)
+ }
+ return success
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetBolusMotorPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetBolusMotorPacket.kt
new file mode 100644
index 0000000000..c41d5fa1a9
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetBolusMotorPacket.kt
@@ -0,0 +1,17 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_BOLUS_MOTOR
+
+class SetBolusMotorPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
+
+ // UNUSED in our driver
+
+ init {
+ opCode = SET_BOLUS_MOTOR.code
+ }
+
+ override fun getRequest(): ByteArray {
+ return byteArrayOf(opCode) + 0.toByte()
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetBolusPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetBolusPacket.kt
new file mode 100644
index 0000000000..4bfc2f1f33
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetBolusPacket.kt
@@ -0,0 +1,23 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_BOLUS
+import info.nightscout.pump.medtrum.extension.toByteArray
+import kotlin.math.round
+
+class SetBolusPacket(injector: HasAndroidInjector, private val insulin: Double) : MedtrumPacket(injector) {
+
+ init {
+ opCode = SET_BOLUS.code
+ }
+
+ override fun getRequest(): ByteArray {
+ // Bolus types:
+ // 1 = normal
+ // 2 = Extended
+ // 3 = Combi
+ val bolusType: Byte = 1 // Only support for normal bolus for now
+ val bolusAmount: Int = round(insulin / 0.05).toInt()
+ return byteArrayOf(opCode) + bolusType + bolusAmount.toByteArray(2) + 0.toByte()
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetPatchPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetPatchPacket.kt
new file mode 100644
index 0000000000..c9196f5bee
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetPatchPacket.kt
@@ -0,0 +1,46 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumPump
+import info.nightscout.pump.medtrum.comm.enums.AlarmSetting
+import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_PATCH
+import info.nightscout.pump.medtrum.extension.toByte
+import info.nightscout.pump.medtrum.extension.toByteArray
+import javax.inject.Inject
+import kotlin.math.round
+
+class SetPatchPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
+
+ @Inject lateinit var medtrumPump: MedtrumPump
+
+ init {
+ opCode = SET_PATCH.code
+ }
+
+ override fun getRequest(): ByteArray {
+ /**
+ * byte 0: opCode
+ * byte 1: alarmSetting // See AlarmSetting
+ * byte 2-3: hourlyMaxInsulin // Max hourly dose of insulin, divided by 0.05
+ * byte 4-5: dailyMaxSet // Max daily dose of insulin, divided by 0.05
+ * byte 6: expirationTimer // Expiration timer, 0 = no expiration 1 = 12 hour reminder and expiration
+ * byte 7: autoSuspendEnable // Value for auto mode, not used for AAPS
+ * byte 8: autoSuspendTime // Value for auto mode, not used for AAPS
+ * byte 9: lowSuspend // Value for auto mode, not used for AAPS
+ * byte 10: predictiveLowSuspend // Value for auto mode, not used for AAPS
+ * byte 11: predictiveLowSuspendRange // Value for auto mode, not used for AAPS
+ */
+
+ val alarmSetting: AlarmSetting = medtrumPump.desiredAlarmSetting
+ val hourlyMaxInsulin: Int = round(medtrumPump.desiredHourlyMaxInsulin / 0.05).toInt()
+ val dailyMaxInsulin: Int = round(medtrumPump.desiredDailyMaxInsulin / 0.05).toInt()
+ val patchExpiration: Byte = medtrumPump.desiredPatchExpiration.toByte()
+ val autoSuspendEnable: Byte = 0
+ val autoSuspendTime: Byte = 12 // Not sure why, but pump needs this
+ val lowSuspend: Byte = 0
+ val predictiveLowSuspend: Byte = 0
+ val predictiveLowSuspendRange: Byte = 30 // Not sure why, but pump needs this
+
+ return byteArrayOf(opCode) + alarmSetting.code + hourlyMaxInsulin.toByteArray(2) + dailyMaxInsulin.toByteArray(2) + patchExpiration + autoSuspendEnable + autoSuspendTime + lowSuspend + predictiveLowSuspend + predictiveLowSuspendRange
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetTempBasalPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetTempBasalPacket.kt
new file mode 100644
index 0000000000..8e392d2ded
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetTempBasalPacket.kt
@@ -0,0 +1,67 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumPump
+import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_TEMP_BASAL
+import info.nightscout.pump.medtrum.comm.enums.BasalType
+import info.nightscout.pump.medtrum.extension.toByteArray
+import info.nightscout.pump.medtrum.extension.toInt
+import info.nightscout.pump.medtrum.extension.toLong
+import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
+import info.nightscout.rx.logging.LTag
+import javax.inject.Inject
+import kotlin.math.round
+
+class SetTempBasalPacket(injector: HasAndroidInjector, private val absoluteRate: Double, private val durationInMinutes: Int) : MedtrumPacket(injector) {
+
+ @Inject lateinit var medtrumPump: MedtrumPump
+
+ companion object {
+
+ private const val RESP_BASAL_TYPE_START = 6
+ private const val RESP_BASAL_TYPE_END = RESP_BASAL_TYPE_START + 1
+ private const val RESP_BASAL_RATE_START = RESP_BASAL_TYPE_END
+ private const val RESP_BASAL_RATE_END = RESP_BASAL_RATE_START + 2
+ private const val RESP_BASAL_SEQUENCE_START = RESP_BASAL_RATE_END
+ private const val RESP_BASAL_SEQUENCE_END = RESP_BASAL_SEQUENCE_START + 2
+ private const val RESP_BASAL_PATCH_ID_START = RESP_BASAL_SEQUENCE_END
+ private const val RESP_BASAL_PATCH_ID_END = RESP_BASAL_PATCH_ID_START + 2
+ private const val RESP_BASAL_START_TIME_START = RESP_BASAL_PATCH_ID_END
+ private const val RESP_BASAL_START_TIME_END = RESP_BASAL_START_TIME_START + 4
+ }
+
+ init {
+ opCode = SET_TEMP_BASAL.code
+ expectedMinRespLength = RESP_BASAL_START_TIME_END
+ }
+
+ override fun getRequest(): ByteArray {
+ /**
+ * byte 0: opCode
+ * byte 1: tempBasalType
+ * byte 2-3: tempBasalRate
+ * byte 4-5: tempBasalDuration
+ */
+ val tempBasalType: Byte = 6 // Fixed to temp basal value for now
+ val tempBasalRate: Int = round(absoluteRate / 0.05).toInt()
+ val tempBasalDuration: Int = durationInMinutes
+ return byteArrayOf(opCode) + tempBasalType + tempBasalRate.toByteArray(2) + tempBasalDuration.toByteArray(2)
+ }
+
+ override fun handleResponse(data: ByteArray): Boolean {
+ val success = super.handleResponse(data)
+ if (success) {
+ val basalType = enumValues()[data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()]
+ val basalRate = data.copyOfRange(RESP_BASAL_RATE_START, RESP_BASAL_RATE_END).toInt() * 0.05
+ val basalSequence = data.copyOfRange(RESP_BASAL_SEQUENCE_START, RESP_BASAL_SEQUENCE_END).toInt()
+ val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toLong()
+
+ val rawTime = data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong()
+ val basalStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong())
+ aapsLogger.debug(LTag.PUMPCOMM, "Basal status update: type=$basalType, rate=$basalRate, sequence=$basalSequence, patchId=$basalPatchId, startTime=$basalStartTime, rawTime=$rawTime")
+
+ medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalStartTime)
+ }
+ return success
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetTimePacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetTimePacket.kt
new file mode 100644
index 0000000000..a7a2c7d7a8
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetTimePacket.kt
@@ -0,0 +1,18 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_TIME
+import info.nightscout.pump.medtrum.extension.toByteArray
+import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
+
+class SetTimePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
+
+ init {
+ opCode = SET_TIME.code
+ }
+
+ override fun getRequest(): ByteArray {
+ val time = MedtrumTimeUtil().getCurrentTimePumpSeconds()
+ return byteArrayOf(opCode) + 2.toByte() + time.toByteArray(4)
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetTimeZonePacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetTimeZonePacket.kt
new file mode 100644
index 0000000000..129e665cf8
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetTimeZonePacket.kt
@@ -0,0 +1,36 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumPump
+import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_TIME_ZONE
+import info.nightscout.pump.medtrum.extension.toByteArray
+import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
+import info.nightscout.shared.utils.DateUtil
+import javax.inject.Inject
+
+class SetTimeZonePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
+
+ @Inject lateinit var dateUtil: DateUtil
+ @Inject lateinit var medtrumPump: MedtrumPump
+
+ var offsetMins: Int = 0
+
+ init {
+ opCode = SET_TIME_ZONE.code
+ }
+
+ override fun getRequest(): ByteArray {
+ val time = MedtrumTimeUtil().getCurrentTimePumpSeconds()
+ offsetMins = dateUtil.getTimeZoneOffsetMinutes(dateUtil.now())
+ if (offsetMins < 0) offsetMins += 65536
+ return byteArrayOf(opCode) + offsetMins.toByteArray(2) + time.toByteArray(4)
+ }
+
+ override fun handleResponse(data: ByteArray): Boolean {
+ val success = super.handleResponse(data)
+ if (success) {
+ medtrumPump.pumpTimeZoneOffset = offsetMins
+ }
+ return success
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/StopPatchPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/StopPatchPacket.kt
new file mode 100644
index 0000000000..dba47867a5
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/StopPatchPacket.kt
@@ -0,0 +1,37 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumPump
+import info.nightscout.pump.medtrum.extension.toInt
+import info.nightscout.pump.medtrum.comm.enums.CommandType.STOP_PATCH
+import info.nightscout.pump.medtrum.extension.toLong
+import javax.inject.Inject
+
+class StopPatchPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
+
+ @Inject lateinit var medtrumPump: MedtrumPump
+
+ companion object {
+
+ private const val RESP_STOP_SEQUENCE_START = 6
+ private const val RESP_STOP_SEQUENCE_END = RESP_STOP_SEQUENCE_START + 2
+ private const val RESP_STOP_PATCH_ID_START = RESP_STOP_SEQUENCE_END
+ private const val RESP_STOP_PATCH_ID_END = RESP_STOP_PATCH_ID_START + 2
+ }
+
+ init {
+ opCode = STOP_PATCH.code
+ expectedMinRespLength = RESP_STOP_PATCH_ID_END
+ }
+
+ override fun handleResponse(data: ByteArray): Boolean {
+ val success = super.handleResponse(data)
+ if (success) {
+ val stopSequence = data.copyOfRange(RESP_STOP_SEQUENCE_START, RESP_STOP_SEQUENCE_END).toInt()
+ val stopPatchId = data.copyOfRange(RESP_STOP_PATCH_ID_START, RESP_STOP_PATCH_ID_END).toLong()
+
+ medtrumPump.handleStopStatusUpdate(stopSequence, stopPatchId)
+ }
+ return success
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SubscribePacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SubscribePacket.kt
new file mode 100644
index 0000000000..c1615a2013
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SubscribePacket.kt
@@ -0,0 +1,16 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.comm.enums.CommandType.SUBSCRIBE
+import info.nightscout.pump.medtrum.extension.toByteArray
+
+class SubscribePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
+
+ init {
+ opCode = SUBSCRIBE.code
+ }
+
+ override fun getRequest(): ByteArray {
+ return byteArrayOf(opCode) + 4095.toByteArray(2)
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacket.kt
new file mode 100644
index 0000000000..4df32dafe7
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacket.kt
@@ -0,0 +1,74 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumPump
+import info.nightscout.pump.medtrum.comm.enums.CommandType.SYNCHRONIZE
+import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
+import info.nightscout.pump.medtrum.extension.toByteArray
+import info.nightscout.pump.medtrum.extension.toInt
+import info.nightscout.rx.logging.LTag
+import javax.inject.Inject
+
+class SynchronizePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
+
+ @Inject lateinit var medtrumPump: MedtrumPump
+
+ companion object {
+
+ private const val RESP_STATE_START = 6
+ private const val RESP_STATE_END = RESP_STATE_START + 1
+ private const val RESP_FIELDS_START = 7
+ private const val RESP_FIELDS_END = RESP_FIELDS_START + 2
+ private const val RESP_SYNC_DATA_START = 9
+
+ private const val MASK_SUSPEND = 0x01
+ private const val MASK_NORMAL_BOLUS = 0x02
+ private const val MASK_EXTENDED_BOLUS = 0x04
+ }
+
+ init {
+ opCode = SYNCHRONIZE.code
+ expectedMinRespLength = RESP_SYNC_DATA_START + 1
+ }
+
+ override fun handleResponse(data: ByteArray): Boolean {
+ val success = super.handleResponse(data)
+ if (success) {
+ var state = MedtrumPumpState.fromByte(data[RESP_STATE_START])
+
+ aapsLogger.debug(LTag.PUMPCOMM, "SynchronizePacket: state: $state")
+ if (state != medtrumPump.pumpState) {
+ aapsLogger.debug(LTag.PUMPCOMM, "State changed from ${medtrumPump.pumpState} to $state")
+ medtrumPump.pumpState = state
+ }
+
+ var fieldMask = data.copyOfRange(RESP_FIELDS_START, RESP_FIELDS_END).toInt()
+ var syncData = data.copyOfRange(RESP_SYNC_DATA_START, data.size)
+ var offset = 0
+
+ if (fieldMask != 0) {
+ aapsLogger.debug(LTag.PUMPCOMM, "SynchronizePacket: fieldMask: $fieldMask")
+ }
+
+ // Remove bolus fields from fieldMask if fields are present (we sync bolus trough other commands)
+ if (fieldMask and MASK_SUSPEND != 0) {
+ offset += 4 // If field is present, skip 4 bytes
+ }
+ if (fieldMask and MASK_NORMAL_BOLUS != 0) {
+ aapsLogger.debug(LTag.PUMPCOMM, "SynchronizePacket: Normal bolus present removing from fieldMask")
+ fieldMask = fieldMask and MASK_NORMAL_BOLUS.inv()
+ syncData = syncData.copyOfRange(0, offset) + syncData.copyOfRange(offset + 3, syncData.size)
+ }
+ if (fieldMask and MASK_EXTENDED_BOLUS != 0) {
+ aapsLogger.debug(LTag.PUMPCOMM, "SynchronizePacket: Extended bolus present removing from fieldMask")
+ fieldMask = fieldMask and MASK_EXTENDED_BOLUS.inv()
+ syncData = syncData.copyOfRange(0, offset) + syncData.copyOfRange(offset + 3, syncData.size)
+ }
+
+ // Let the notification packet handle the rest of the sync data
+ NotificationPacket(injector).handleMaskedMessage(fieldMask.toByteArray(2) + syncData)
+ }
+
+ return success
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/di/MedtrumCommModule.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/di/MedtrumCommModule.kt
new file mode 100644
index 0000000000..d58d9d2f33
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/di/MedtrumCommModule.kt
@@ -0,0 +1,57 @@
+package info.nightscout.pump.medtrum.di
+
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+import info.nightscout.pump.medtrum.comm.packets.ActivatePacket
+import info.nightscout.pump.medtrum.comm.packets.AuthorizePacket
+import info.nightscout.pump.medtrum.comm.packets.CancelBolusPacket
+import info.nightscout.pump.medtrum.comm.packets.CancelTempBasalPacket
+import info.nightscout.pump.medtrum.comm.packets.ClearPumpAlarmPacket
+import info.nightscout.pump.medtrum.comm.packets.GetDeviceTypePacket
+import info.nightscout.pump.medtrum.comm.packets.GetRecordPacket
+import info.nightscout.pump.medtrum.comm.packets.GetTimePacket
+import info.nightscout.pump.medtrum.comm.packets.MedtrumPacket
+import info.nightscout.pump.medtrum.comm.packets.NotificationPacket
+import info.nightscout.pump.medtrum.comm.packets.PollPatchPacket
+import info.nightscout.pump.medtrum.comm.packets.PrimePacket
+import info.nightscout.pump.medtrum.comm.packets.ReadBolusStatePacket
+import info.nightscout.pump.medtrum.comm.packets.ResumePumpPacket
+import info.nightscout.pump.medtrum.comm.packets.SetBasalProfilePacket
+import info.nightscout.pump.medtrum.comm.packets.SetBolusMotorPacket
+import info.nightscout.pump.medtrum.comm.packets.SetBolusPacket
+import info.nightscout.pump.medtrum.comm.packets.SetPatchPacket
+import info.nightscout.pump.medtrum.comm.packets.SetTempBasalPacket
+import info.nightscout.pump.medtrum.comm.packets.SetTimePacket
+import info.nightscout.pump.medtrum.comm.packets.SetTimeZonePacket
+import info.nightscout.pump.medtrum.comm.packets.StopPatchPacket
+import info.nightscout.pump.medtrum.comm.packets.SubscribePacket
+import info.nightscout.pump.medtrum.comm.packets.SynchronizePacket
+
+@Module
+abstract class MedtrumCommModule {
+
+ @ContributesAndroidInjector abstract fun contributesActivatePacket(): ActivatePacket
+ @ContributesAndroidInjector abstract fun contributesAuthorizePacket(): AuthorizePacket
+ @ContributesAndroidInjector abstract fun contributesCancelBolusPacket(): CancelBolusPacket
+ @ContributesAndroidInjector abstract fun contributesCancelTempBasalPacket(): CancelTempBasalPacket
+ @ContributesAndroidInjector abstract fun contributesClearPumpAlarmPacket(): ClearPumpAlarmPacket
+ @ContributesAndroidInjector abstract fun contributesGetDeviceTypePacket(): GetDeviceTypePacket
+ @ContributesAndroidInjector abstract fun contributesGetRecordPacket(): GetRecordPacket
+ @ContributesAndroidInjector abstract fun contributesGetTimePacket(): GetTimePacket
+ @ContributesAndroidInjector abstract fun contributesMedtrumPacket(): MedtrumPacket
+ @ContributesAndroidInjector abstract fun contributesNotificationPacket(): NotificationPacket
+ @ContributesAndroidInjector abstract fun contributesPollPatchPacket(): PollPatchPacket
+ @ContributesAndroidInjector abstract fun contributesPrimePacket(): PrimePacket
+ @ContributesAndroidInjector abstract fun contributesReadBolusStatePacket(): ReadBolusStatePacket
+ @ContributesAndroidInjector abstract fun contributesResumePumpPacket(): ResumePumpPacket
+ @ContributesAndroidInjector abstract fun contributesSetBasalProfilePacket(): SetBasalProfilePacket
+ @ContributesAndroidInjector abstract fun contributesSetBolusMotorPacket(): SetBolusMotorPacket
+ @ContributesAndroidInjector abstract fun contributesSetBolusPacket(): SetBolusPacket
+ @ContributesAndroidInjector abstract fun contributesSetPatchPacket(): SetPatchPacket
+ @ContributesAndroidInjector abstract fun contributesSetTempBasalPacket(): SetTempBasalPacket
+ @ContributesAndroidInjector abstract fun contributesSetTimePacket(): SetTimePacket
+ @ContributesAndroidInjector abstract fun contributesSetTimeZonePacket(): SetTimeZonePacket
+ @ContributesAndroidInjector abstract fun contributesStopPatchPacket(): StopPatchPacket
+ @ContributesAndroidInjector abstract fun contributesSubscribePacket(): SubscribePacket
+ @ContributesAndroidInjector abstract fun contributesSynchronizePacket(): SynchronizePacket
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/di/MedtrumInjectHelpers.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/di/MedtrumInjectHelpers.kt
new file mode 100644
index 0000000000..96118f4670
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/di/MedtrumInjectHelpers.kt
@@ -0,0 +1,12 @@
+package info.nightscout.pump.medtrum.di
+
+import javax.inject.Qualifier
+import javax.inject.Scope
+
+@Qualifier
+annotation class MedtrumPluginQualifier
+
+@MustBeDocumented
+@Scope
+@Retention(AnnotationRetention.RUNTIME)
+annotation class FragmentScope
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/di/MedtrumModule.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/di/MedtrumModule.kt
new file mode 100644
index 0000000000..bc5c286b46
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/di/MedtrumModule.kt
@@ -0,0 +1,121 @@
+package info.nightscout.pump.medtrum.di
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.android.ContributesAndroidInjector
+import dagger.multibindings.IntoMap
+import info.nightscout.pump.medtrum.services.MedtrumService
+import info.nightscout.pump.medtrum.ui.MedtrumActivateCompleteFragment
+import info.nightscout.pump.medtrum.ui.MedtrumActivateFragment
+import info.nightscout.pump.medtrum.ui.MedtrumAttachPatchFragment
+import info.nightscout.pump.medtrum.ui.MedtrumDeactivatePatchFragment
+import info.nightscout.pump.medtrum.ui.MedtrumDeactivationCompleteFragment
+import info.nightscout.pump.medtrum.ui.MedtrumPreparePatchConnectFragment
+import info.nightscout.pump.medtrum.ui.MedtrumPreparePatchFragment
+import info.nightscout.pump.medtrum.ui.MedtrumPrimeCompleteFragment
+import info.nightscout.pump.medtrum.ui.MedtrumPrimeFragment
+import info.nightscout.pump.medtrum.ui.MedtrumPrimingFragment
+import info.nightscout.pump.medtrum.ui.MedtrumStartDeactivationFragment
+import info.nightscout.pump.medtrum.ui.MedtrumActivity
+import info.nightscout.pump.medtrum.ui.MedtrumOverviewFragment
+import info.nightscout.pump.medtrum.ui.MedtrumRetryActivationConnectFragment
+import info.nightscout.pump.medtrum.ui.MedtrumRetryActivationFragment
+import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumOverviewViewModel
+import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
+import info.nightscout.pump.medtrum.ui.viewmodel.ViewModelFactory
+import info.nightscout.pump.medtrum.ui.viewmodel.ViewModelKey
+import javax.inject.Provider
+
+@Module(includes = [MedtrumCommModule::class])
+@Suppress("unused")
+abstract class MedtrumModule {
+
+ companion object {
+
+ @Provides
+ @MedtrumPluginQualifier
+ fun providesViewModelFactory(@MedtrumPluginQualifier viewModels: MutableMap, @JvmSuppressWildcards Provider>): ViewModelProvider.Factory {
+ return ViewModelFactory(viewModels)
+ }
+ }
+
+ // VIEW MODELS
+ @Binds
+ @IntoMap
+ @MedtrumPluginQualifier
+ @ViewModelKey(MedtrumOverviewViewModel::class)
+ internal abstract fun bindsMedtrumOverviewViewmodel(viewModel: MedtrumOverviewViewModel): ViewModel
+
+ @Binds
+ @IntoMap
+ @MedtrumPluginQualifier
+ @ViewModelKey(MedtrumViewModel::class)
+ internal abstract fun bindsMedtrumViewModel(viewModel: MedtrumViewModel): ViewModel
+
+ // FRAGMENTS
+ @ContributesAndroidInjector
+ abstract fun contributesMedtrumOverviewFragment(): MedtrumOverviewFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesStartDeactivationFragment(): MedtrumStartDeactivationFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesDeactivatePatchFragment(): MedtrumDeactivatePatchFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesDeactivationCompleteFragment(): MedtrumDeactivationCompleteFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesPreparePatchFragment(): MedtrumPreparePatchFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesPreparePatchConnectFragment(): MedtrumPreparePatchConnectFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesRetryActivationFragment(): MedtrumRetryActivationFragment
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesRetryActivationConnectFragment(): MedtrumRetryActivationConnectFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesPrimeFragment(): MedtrumPrimeFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesPrimeCompleteFragment(): MedtrumPrimeCompleteFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesPrimingFragment(): MedtrumPrimingFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesAttachPatchFragment(): MedtrumAttachPatchFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesActivateFragment(): MedtrumActivateFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesActivateCompleteFragment(): MedtrumActivateCompleteFragment
+
+ // ACTIVITIES
+ @ContributesAndroidInjector
+ abstract fun contributesMedtrumActivity(): MedtrumActivity
+
+ // SERVICE
+ @ContributesAndroidInjector
+ abstract fun contributesMedtrumService(): MedtrumService
+
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/encryption/Crypt.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/encryption/Crypt.kt
new file mode 100644
index 0000000000..ce5126de48
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/encryption/Crypt.kt
@@ -0,0 +1,74 @@
+package info.nightscout.pump.medtrum.encryption
+
+import info.nightscout.pump.medtrum.extension.toByteArray
+import info.nightscout.pump.medtrum.extension.toLong
+
+class Crypt {
+
+ private val RIJNDEAL_S_BOX: IntArray = intArrayOf(99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118, 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192, 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216, 49, 21, 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117, 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132, 83, 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207, 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186, 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, 140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22)
+ private val RIJNDEAL_INVERSE_S_BOX: IntArray = intArrayOf(82, 9, 106, 213, 48, 54, 165, 56, 191, 64, 163, 158, 129, 243, 215, 251, 124, 227, 57, 130, 155, 47, 255, 135, 52, 142, 67, 68, 196, 222, 233, 203, 84, 123, 148, 50, 166, 194, 35, 61, 238, 76, 149, 11, 66, 250, 195, 78, 8, 46, 161, 102, 40, 217, 36, 178, 118, 91, 162, 73, 109, 139, 209, 37, 114, 248, 246, 100, 134, 104, 152, 22, 212, 164, 92, 204, 93, 101, 182, 146, 108, 112, 72, 80, 253, 237, 185, 218, 94, 21, 70, 87, 167, 141, 157, 132, 144, 216, 171, 0, 140, 188, 211, 10, 247, 228, 88, 5, 184, 179, 69, 6, 208, 44, 30, 143, 202, 63, 15, 2, 193, 175, 189, 3, 1, 19, 138, 107, 58, 145, 17, 65, 79, 103, 220, 234, 151, 242, 207, 206, 240, 180, 230, 115, 150, 172, 116, 34, 231, 173, 53, 133, 226, 249, 55, 232, 28, 117, 223, 110, 71, 241, 26, 113, 29, 41, 197, 137, 111, 183, 98, 14, 170, 24, 190, 27, 252, 86, 62, 75, 198, 210, 121, 32, 154, 219, 192, 254, 120, 205, 90, 244, 31, 221, 168, 51, 136, 7, 199, 49, 177, 18, 16, 89, 39, 128, 236, 95, 96, 81, 127, 169, 25, 181, 74, 13, 45, 229, 122, 159, 147, 201, 156, 239, 160, 224, 59, 77, 174, 42, 245, 176, 200, 235, 187, 60, 131, 83, 153, 97, 23, 43, 4, 126, 186, 119, 214, 38, 225, 105, 20, 99, 85, 33, 12, 125)
+
+ val MED_CIPHER: Long = 1344751489
+
+ fun keyGen(input: Long): Long {
+ val key = randomGen(randomGen(MED_CIPHER xor input))
+ return simpleCrypt(key)
+ }
+
+ fun generateRandomToken(): Long {
+ val randomBytes = ByteArray(4)
+ val random = java.security.SecureRandom()
+ random.nextBytes(randomBytes)
+ return randomBytes.toLong()
+ }
+
+ private fun simpleCrypt(inputData: Long): Long {
+ var temp = inputData xor MED_CIPHER
+ for (i in 0 until 32) {
+ temp = changeByTable(rotatoLeft(temp, 32, 1), RIJNDEAL_S_BOX).toLong()
+ }
+ return temp
+ }
+
+ fun simpleDecrypt(inputData: Long): Long {
+ var temp = inputData
+ for (i in 0 until 32) {
+ temp = rotatoRight(changeByTable(temp, RIJNDEAL_INVERSE_S_BOX), 32, 1).toLong()
+ }
+ return temp xor MED_CIPHER
+ }
+
+ private fun randomGen(input: Long): Long {
+ val A = 16807
+ val Q = 127773
+ val R = 2836
+ val tmp1 = input / Q
+ var ret = (input - (tmp1 * Q)) * A - (tmp1 * R)
+ if (ret < 0) {
+ ret += 2147483647L
+ }
+ return ret
+ }
+
+ private fun changeByTable(inputData: Long, tableData: IntArray): Long {
+ val value = inputData.toByteArray(4)
+ val results = ByteArray(4)
+
+ for (i in value.indices) {
+ var byte = value[i].toInt()
+ if (byte < 0) {
+ byte += 256
+ }
+ results[i] = tableData[byte].toByte()
+ }
+ return results.toLong()
+ }
+
+ private fun rotatoLeft(x: Long, s: Int, n: Int): Long {
+ return (x shl n) or (x ushr (s - n))
+ }
+
+ private fun rotatoRight(x: Long, s: Int, n: Int): Int {
+ return (x ushr n or (x shl (s - n))).toInt()
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/AppCompatActivityExtension.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/AppCompatActivityExtension.kt
new file mode 100644
index 0000000000..fd4c50713c
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/AppCompatActivityExtension.kt
@@ -0,0 +1,19 @@
+package info.nightscout.pump.medtrum.extension
+
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentTransaction
+
+fun AppCompatActivity.replaceFragmentInActivity(fragment: Fragment, frameId: Int, addToBackStack: Boolean = false) {
+ supportFragmentManager.transact {
+ replace(frameId, fragment)
+ if (addToBackStack) addToBackStack(null)
+ }
+}
+
+private inline fun FragmentManager.transact(action: FragmentTransaction.() -> Unit) {
+ beginTransaction().apply {
+ action()
+ }.commit()
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/BoolExtension.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/BoolExtension.kt
new file mode 100644
index 0000000000..35728d68fd
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/BoolExtension.kt
@@ -0,0 +1,8 @@
+package info.nightscout.pump.medtrum.extension
+
+fun Boolean.toByte(): Byte {
+ return if (this == true)
+ 0x1
+ else
+ 0x0
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/ByteArrayExtension.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/ByteArrayExtension.kt
new file mode 100644
index 0000000000..4e1d431086
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/ByteArrayExtension.kt
@@ -0,0 +1,41 @@
+package info.nightscout.pump.medtrum.extension
+
+/** Extensions for different types of conversions needed when doing stuff with bytes */
+fun ByteArray.toLong(): Long {
+ require(this.size <= 8) {
+ "Array size must be <= 8 for 'toLong' conversion operation"
+ }
+ var result = 0L
+ for (i in this.indices) {
+ val byte = this[i]
+ val shifted = (byte.toInt() and 0xFF).toLong() shl 8 * i
+ result = result or shifted
+ }
+ return result
+}
+
+fun ByteArray.toInt(): Int {
+ require(this.size <= 4) {
+ "Array size must be <= 4 for 'toInt' conversion operation"
+ }
+ var result = 0
+ for (i in this.indices) {
+ val byte = this[i]
+ val shifted = (byte.toInt() and 0xFF).toInt() shl 8 * i
+ result = result or shifted
+ }
+ return result
+}
+
+fun ByteArray.toFloat(): Float {
+ require(this.size == 4) {
+ "Array size must be == 4 for 'toFloat' conversion operation"
+ }
+ var asInt = 0
+ for (i in this.indices) {
+ val byte = this[i]
+ val shifted = (byte.toInt() and 0xFF) shl 8 * i
+ asInt = asInt or shifted
+ }
+ return Float.fromBits(asInt)
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/IntExtension.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/IntExtension.kt
new file mode 100644
index 0000000000..8589496e18
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/IntExtension.kt
@@ -0,0 +1,10 @@
+package info.nightscout.pump.medtrum.extension
+
+/** Extensions for different types of conversions needed when doing stuff with bytes */
+fun Int.toByteArray(byteLength: Int): ByteArray {
+ val bytes = ByteArray(byteLength)
+ for (i in 0 until byteLength) {
+ bytes[i] = (this shr (i * 8) and 0xFF).toByte()
+ }
+ return bytes
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/LongExtension.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/LongExtension.kt
new file mode 100644
index 0000000000..a294670ec1
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/LongExtension.kt
@@ -0,0 +1,10 @@
+package info.nightscout.pump.medtrum.extension
+
+/** Extensions for different types of conversions needed when doing stuff with bytes */
+fun Long.toByteArray(byteLength: Int): ByteArray {
+ val bytes = ByteArray(byteLength)
+ for (i in 0 until byteLength) {
+ bytes[i] = (this shr (i * 8) and 0xFF).toByte()
+ }
+ return bytes
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/ViewExtension.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/ViewExtension.kt
new file mode 100644
index 0000000000..5cf65baeb6
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/extension/ViewExtension.kt
@@ -0,0 +1,34 @@
+package info.nightscout.pump.medtrum.extension
+
+import android.view.View
+
+fun View?.visible() = this?.run { visibility = View.VISIBLE }
+
+fun View?.visible(vararg views: View?) {
+ visible()
+ for (view in views)
+ view.visible()
+}
+
+fun View?.invisible() = this?.run { visibility = View.INVISIBLE }
+
+fun View?.invisible(vararg views: View?) {
+ invisible()
+ for (view in views)
+ view.invisible()
+}
+
+fun View?.gone() = this?.run { visibility = View.GONE }
+
+fun View?.gone(vararg views: View?) {
+ gone()
+ for (view in views)
+ view.gone()
+}
+
+fun View?.setVisibleOrGone(visibleOrGone: Boolean, vararg views: View?) {
+ for (view in views)
+ if (visibleOrGone) view.visible() else view.gone()
+}
+
+fun View?.setVisibleOrGone(visibleOrGone: Boolean) = setVisibleOrGone(visibleOrGone, this)
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/BLEComm.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/BLEComm.kt
new file mode 100644
index 0000000000..b4058efd49
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/BLEComm.kt
@@ -0,0 +1,475 @@
+package info.nightscout.pump.medtrum.services
+
+import android.Manifest
+import android.annotation.SuppressLint
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothGatt
+import android.bluetooth.BluetoothGattCallback
+import android.bluetooth.BluetoothGattCharacteristic
+import android.bluetooth.BluetoothGattDescriptor
+import android.bluetooth.BluetoothGattService
+import android.bluetooth.BluetoothManager
+import android.bluetooth.BluetoothProfile
+import android.bluetooth.le.ScanCallback
+import android.bluetooth.le.ScanResult
+import android.bluetooth.le.ScanSettings
+import android.bluetooth.le.ScanFilter
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.SystemClock
+import androidx.core.app.ActivityCompat
+import info.nightscout.core.ui.toast.ToastUtils
+import info.nightscout.pump.medtrum.extension.toInt
+import info.nightscout.pump.medtrum.comm.WriteCommandPackets
+import info.nightscout.pump.medtrum.comm.ManufacturerData
+import info.nightscout.pump.medtrum.comm.ReadDataPacket
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import java.util.UUID
+import java.util.Arrays
+import javax.inject.Inject
+import javax.inject.Singleton
+
+interface BLECommCallback {
+
+ fun onBLEConnected()
+ fun onBLEDisconnected()
+ fun onNotification(notification: ByteArray)
+ fun onIndication(indication: ByteArray)
+ fun onSendMessageError(reason: String)
+}
+
+@Singleton
+class BLEComm @Inject internal constructor(
+ private val aapsLogger: AAPSLogger,
+ private val context: Context
+) {
+
+ companion object {
+
+ private const val WRITE_DELAY_MILLIS: Long = 10
+ private const val SERVICE_UUID = "669A9001-0008-968F-E311-6050405558B3"
+ private const val READ_UUID = "669a9120-0008-968f-e311-6050405558b3"
+ private const val WRITE_UUID = "669a9101-0008-968f-e311-6050405558b3"
+ private const val CONFIG_UUID = "00002902-0000-1000-8000-00805f9b34fb"
+
+ private const val NEEDS_ENABLE_NOTIFICATION = 0x10
+ private const val NEEDS_ENABLE_INDICATION = 0x20
+ private const val NEEDS_ENABLE = 0x30
+
+ private const val MANUFACTURER_ID = 18305
+ }
+
+ private val handler =
+ Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper)
+ private val mBluetoothAdapter: BluetoothAdapter? get() = (context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?)?.adapter
+ private var mBluetoothGatt: BluetoothGatt? = null
+
+ private var isConnected = false // Only to track internal ble state
+ private var isConnecting = false // Only to track internal ble state
+ private var uartWrite: BluetoothGattCharacteristic? = null
+ private var uartRead: BluetoothGattCharacteristic? = null
+
+ // Read and write buffers
+ private var mWritePackets: WriteCommandPackets? = null
+ private var mWriteSequenceNumber: Int = 0
+ private var mReadPacket: ReadDataPacket? = null
+ private val readLock = Any()
+
+ private var mDeviceSN: Long = 0
+ private var mCallback: BLECommCallback? = null
+ private var mDevice: BluetoothDevice? = null
+
+ fun setCallback(callback: BLECommCallback?) {
+ this.mCallback = callback
+ }
+
+ /** Connect flow: 1. Start scanning for our device (SN entered in settings) */
+ @SuppressLint("MissingPermission")
+ @Synchronized
+ fun startScan(): Boolean {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
+ ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED
+ ) {
+ ToastUtils.errorToast(context, context.getString(info.nightscout.core.ui.R.string.need_connect_permission))
+ aapsLogger.error(LTag.PUMPBTCOMM, "missing permissions")
+ return false
+ }
+ aapsLogger.debug(LTag.PUMPBTCOMM, "Start scan!!")
+ val settings = ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
+ .build()
+ val filters = mutableListOf()
+
+ // Find our Medtrum Device!
+ filters.add(
+ ScanFilter.Builder().setDeviceName("MT").build()
+ )
+ mBluetoothAdapter?.bluetoothLeScanner?.startScan(filters, settings, mScanCallback)
+ return true
+ }
+
+ @SuppressLint("MissingPermission")
+ @Synchronized
+ fun stopScan() {
+ mBluetoothAdapter?.bluetoothLeScanner?.stopScan(mScanCallback)
+ }
+
+ @Synchronized
+ fun connect(from: String, deviceSN: Long): Boolean {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
+ ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED
+ ) {
+ ToastUtils.errorToast(context, context.getString(info.nightscout.core.ui.R.string.need_connect_permission))
+ aapsLogger.error(LTag.PUMPBTCOMM, "missing permission: $from")
+ return false
+ }
+ aapsLogger.debug(LTag.PUMPBTCOMM, "Initializing BLEComm.")
+ if (mBluetoothAdapter == null) {
+ aapsLogger.error("Unable to obtain a BluetoothAdapter.")
+ return false
+ }
+
+ isConnected = false
+ isConnecting = true
+ mWritePackets = null
+ mReadPacket = null
+
+ if (mDevice != null && mDeviceSN == deviceSN) {
+ // Skip scanning and directly connect to gatt
+ aapsLogger.debug(LTag.PUMPBTCOMM, "Skipping scan and directly connecting to gatt")
+ connectGatt(mDevice!!)
+ } else {
+ // Scan for device
+ aapsLogger.debug(LTag.PUMPBTCOMM, "Scanning for device")
+ mDeviceSN = deviceSN
+ startScan()
+ }
+
+ return true
+ }
+
+ /** Connect flow: 2. When device is found this is called by onScanResult() */
+ @SuppressLint("MissingPermission")
+ @Synchronized
+ private fun connectGatt(device: BluetoothDevice) {
+ // Reset sequence counter
+ mWriteSequenceNumber = 0
+ if (mBluetoothGatt == null) {
+ mBluetoothGatt = device.connectGatt(context, false, mGattCallback, BluetoothDevice.TRANSPORT_LE)
+ } else {
+ // Already connected?, this should not happen force disconnect
+ aapsLogger.error(LTag.PUMPBTCOMM, "connectGatt, mBluetoothGatt is not null")
+ disconnect("connectGatt, mBluetoothGatt is not null")
+ }
+ }
+
+ @SuppressLint("MissingPermission")
+ @Synchronized
+ fun disconnect(from: String) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
+ ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED
+ ) {
+ aapsLogger.error(LTag.PUMPBTCOMM, "missing permission: $from")
+ return
+ }
+ aapsLogger.debug(LTag.PUMPBTCOMM, "disconnect from: $from")
+ if (isConnecting) {
+ isConnecting = false
+ stopScan()
+ SystemClock.sleep(100)
+ }
+ if (isConnected) {
+ aapsLogger.debug(LTag.PUMPBTCOMM, "Connected, disconnecting")
+ mBluetoothGatt?.disconnect()
+ } else {
+ aapsLogger.debug(LTag.PUMPBTCOMM, "Not connected, closing gatt")
+ close()
+ isConnected = false
+ mCallback?.onBLEDisconnected()
+ }
+ }
+
+ @SuppressLint("MissingPermission")
+ @Synchronized fun close() {
+ aapsLogger.debug(LTag.PUMPBTCOMM, "BluetoothAdapter close")
+ mBluetoothGatt?.close()
+ SystemClock.sleep(100)
+ mBluetoothGatt = null
+ }
+
+ /** Scan callback */
+ private val mScanCallback: ScanCallback = object : ScanCallback() {
+ override fun onScanResult(callbackType: Int, result: ScanResult) {
+ aapsLogger.debug(LTag.PUMPBTCOMM, "OnScanResult!" + result)
+ super.onScanResult(callbackType, result)
+
+ val manufacturerData =
+ result.scanRecord?.getManufacturerSpecificData(MANUFACTURER_ID)
+ ?.let { ManufacturerData(it) }
+
+ aapsLogger.debug(LTag.PUMPBTCOMM, "Found deviceSN: " + manufacturerData?.getDeviceSN())
+
+ if (manufacturerData?.getDeviceSN() == mDeviceSN) {
+ aapsLogger.debug(LTag.PUMPBTCOMM, "Found our device! deviceSN: " + manufacturerData.getDeviceSN())
+ stopScan()
+ mDevice = result.device
+ connectGatt(result.device)
+ }
+ }
+
+ override fun onScanFailed(errorCode: Int) {
+ aapsLogger.debug(LTag.PUMPBTCOMM, "Scan FAILED!")
+ }
+ }
+
+ @Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
+ private val mGattCallback: BluetoothGattCallback = object : BluetoothGattCallback() {
+ override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
+ onConnectionStateChangeSynchronized(gatt, status, newState) // call it synchronized
+ }
+
+ override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
+ aapsLogger.debug(LTag.PUMPBTCOMM, "onServicesDiscovered")
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ findCharacteristic()
+ }
+ }
+
+ override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
+ aapsLogger.debug(LTag.PUMPBTCOMM, "onCharacteristicRead data: " + characteristic.value.contentToString() + " UUID: " + characteristic.getUuid().toString() + " status: " + status)
+ }
+
+ override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
+ aapsLogger.debug(LTag.PUMPBTCOMM, "onCharacteristicChanged data: " + characteristic.value.contentToString() + " UUID: " + characteristic.getUuid().toString())
+
+ val value = characteristic.getValue()
+ if (characteristic.getUuid() == UUID.fromString(READ_UUID)) {
+ mCallback?.onNotification(value)
+ } else if (characteristic.getUuid() == UUID.fromString(WRITE_UUID)) {
+ synchronized(readLock) {
+ if (mReadPacket == null) {
+ mReadPacket = ReadDataPacket(value)
+ } else {
+ mReadPacket?.addData(value)
+ }
+ if (mReadPacket?.allDataReceived() == true) {
+ mReadPacket?.getData()?.let { mCallback?.onIndication(it) }
+ mReadPacket = null
+ }
+ }
+ }
+ }
+
+ override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
+ aapsLogger.debug(LTag.PUMPBTCOMM, "onCharacteristicWrite status = " + status)
+
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ // Check if we need to finish our command!
+ mWritePackets?.let {
+ synchronized(it) {
+ val value: ByteArray? = mWritePackets?.getNextPacket()
+ if (value != null) {
+ writeCharacteristic(uartWriteBTGattChar, value)
+ }
+ }
+ }
+ } else {
+ mCallback?.onSendMessageError("onCharacteristicWrite failure")
+ }
+ }
+
+ override fun onDescriptorWrite(gatt: BluetoothGatt?, descriptor: BluetoothGattDescriptor?, status: Int) {
+ super.onDescriptorWrite(gatt, descriptor, status)
+ aapsLogger.debug(LTag.PUMPBTCOMM, "onDescriptorWrite " + status)
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ readDescriptor(descriptor)
+ }
+ }
+
+ /** Connect flow: 5. Notifications enabled read descriptor to verify and start auth process*/
+ override fun onDescriptorRead(
+ gatt: BluetoothGatt,
+ descriptor: BluetoothGattDescriptor,
+ status: Int
+ ) {
+ super.onDescriptorRead(gatt, descriptor, status)
+ aapsLogger.debug(LTag.PUMPBTCOMM, "onDescriptorRead status: " + status)
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ checkDescriptor(descriptor)
+ }
+ }
+ }
+
+ @SuppressLint("MissingPermission")
+ @Synchronized
+ private fun readDescriptor(descriptor: BluetoothGattDescriptor?) {
+ aapsLogger.debug(LTag.PUMPBTCOMM, "readDescriptor")
+ if (mBluetoothAdapter == null || mBluetoothGatt == null || descriptor == null) {
+ aapsLogger.error("BluetoothAdapter not initialized_ERROR")
+ isConnecting = false
+ isConnected = false
+ return
+ }
+ mBluetoothGatt?.readDescriptor(descriptor)
+ }
+
+ @Suppress("DEPRECATION")
+ private fun checkDescriptor(descriptor: BluetoothGattDescriptor) {
+ aapsLogger.debug(LTag.PUMPBTCOMM, "checkDescriptor")
+ val service = getGattService()
+ if (mBluetoothAdapter == null || mBluetoothGatt == null || service == null) {
+ aapsLogger.error("BluetoothAdapter not initialized_ERROR")
+ isConnecting = false
+ isConnected = false
+ return
+ }
+ if (descriptor.value.toInt() > 0) {
+ var notificationEnabled = true
+ val characteristics = service.characteristics
+ for (j in 0 until characteristics.size) {
+ val configDescriptor =
+ characteristics[j].getDescriptor(UUID.fromString(CONFIG_UUID))
+ if (configDescriptor.value == null || configDescriptor.value.toInt() <= 0) {
+ notificationEnabled = false
+ }
+ }
+ if (notificationEnabled) {
+ aapsLogger.debug(LTag.PUMPBTCOMM, "Notifications enabled!")
+ /** Connect flow: 6. Connected */
+ mCallback?.onBLEConnected()
+ }
+ }
+ }
+
+ @Suppress("DEPRECATION")
+ @SuppressLint("MissingPermission")
+ @Synchronized
+ private fun setCharacteristicNotification(characteristic: BluetoothGattCharacteristic?, enabled: Boolean) {
+ aapsLogger.debug(LTag.PUMPBTCOMM, "setCharacteristicNotification")
+ if (mBluetoothAdapter == null || mBluetoothGatt == null) {
+ aapsLogger.error("BluetoothAdapter not initialized_ERROR")
+ isConnecting = false
+ isConnected = false
+ return
+ }
+ mBluetoothGatt?.setCharacteristicNotification(characteristic, enabled)
+ characteristic?.getDescriptor(UUID.fromString(CONFIG_UUID))?.let {
+ if (characteristic.properties and NEEDS_ENABLE_NOTIFICATION > 0) {
+ it.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
+ mBluetoothGatt?.writeDescriptor(it)
+ } else if (characteristic.properties and NEEDS_ENABLE_INDICATION > 0) {
+ it.value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
+ mBluetoothGatt?.writeDescriptor(it)
+ } else {
+
+ }
+ }
+ }
+
+ /** Connect flow: 3. When we are connected discover services*/
+ @SuppressLint("MissingPermission")
+ @Synchronized
+ private fun onConnectionStateChangeSynchronized(gatt: BluetoothGatt, status: Int, newState: Int) {
+ aapsLogger.debug(LTag.PUMPBTCOMM, "onConnectionStateChange newState: " + newState + " status: " + status)
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ isConnected = true
+ isConnecting = false
+ mBluetoothGatt?.discoverServices()
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ if (isConnecting) {
+ // When we are disconnected during connecting, we reset the device address to force a new scan
+ aapsLogger.warn(LTag.PUMPBTCOMM, "Disconnected while connecting! Reset device address")
+ mDevice = null
+ // Wait a bit before retrying
+ SystemClock.sleep(2000)
+ }
+ close()
+ isConnected = false
+ isConnecting = false
+ mCallback?.onBLEDisconnected()
+ aapsLogger.debug(LTag.PUMPBTCOMM, "Device was disconnected " + gatt.device.name) //Device was disconnected
+ }
+ }
+
+ @Synchronized
+ fun sendMessage(message: ByteArray) {
+ aapsLogger.debug(LTag.PUMPBTCOMM, "sendMessage message = " + Arrays.toString(message))
+ if (mWritePackets?.allPacketsConsumed() == false) {
+ aapsLogger.error(LTag.PUMPBTCOMM, "sendMessage not all packets consumed!! unable to sent message!")
+ return
+ }
+ mWritePackets = WriteCommandPackets(message, mWriteSequenceNumber)
+ mWriteSequenceNumber = (mWriteSequenceNumber + 1) % 256
+ val value: ByteArray? = mWritePackets?.getNextPacket()
+ if (value != null) {
+ writeCharacteristic(uartWriteBTGattChar, value)
+ } else {
+ aapsLogger.error(LTag.PUMPBTCOMM, "sendMessage error in writePacket!")
+ mCallback?.onSendMessageError("error in writePacket!")
+ }
+ }
+
+ private fun getGattService(): BluetoothGattService? {
+ aapsLogger.debug(LTag.PUMPBTCOMM, "getGattService")
+ if (mBluetoothAdapter == null || mBluetoothGatt == null) {
+ aapsLogger.error("BluetoothAdapter not initialized_ERROR")
+ isConnecting = false
+ isConnected = false
+ return null
+ }
+ return mBluetoothGatt?.getService(UUID.fromString(SERVICE_UUID))
+ }
+
+ @Suppress("DEPRECATION")
+ @SuppressLint("MissingPermission")
+ @Synchronized
+ private fun writeCharacteristic(characteristic: BluetoothGattCharacteristic, data: ByteArray?) {
+ handler.postDelayed({
+ if (mBluetoothAdapter == null || mBluetoothGatt == null) {
+ aapsLogger.error("BluetoothAdapter not initialized_ERROR")
+ isConnecting = false
+ isConnected = false
+ } else {
+ characteristic.value = data
+ characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
+ aapsLogger.debug(LTag.PUMPBTCOMM, "writeCharacteristic: ${Arrays.toString(data)}")
+ val success = mBluetoothGatt?.writeCharacteristic(characteristic)
+ if (success != true) {
+ mCallback?.onSendMessageError("Failed to write characteristic")
+ }
+ }
+ }, WRITE_DELAY_MILLIS)
+ }
+
+ private val uartWriteBTGattChar: BluetoothGattCharacteristic
+ get() = uartWrite
+ ?: BluetoothGattCharacteristic(UUID.fromString(WRITE_UUID), BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT, 0).also { uartWrite = it }
+
+ /** Connect flow: 4. When services are discovered find characteristics and set notifications*/
+ private fun findCharacteristic() {
+ val gattService = getGattService() ?: return
+ var uuid: String
+ val gattCharacteristics = gattService.characteristics
+ for (i in 0..gattCharacteristics.size - 1) {
+ val gattCharacteristic = gattCharacteristics.get(i)
+ // Check whether read or write properties is set, the pump needs us to enable notifications on all characteristics that have these properties
+ if (gattCharacteristic.properties and NEEDS_ENABLE > 0) {
+ handler.postDelayed({
+ uuid = gattCharacteristic.uuid.toString()
+ setCharacteristicNotification(gattCharacteristic, true)
+ if (READ_UUID == uuid) {
+ uartRead = gattCharacteristic
+ }
+ if (WRITE_UUID == uuid) {
+ uartWrite = gattCharacteristic
+ }
+ }, (i * 600).toLong())
+ }
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt
new file mode 100644
index 0000000000..c8d793c36c
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt
@@ -0,0 +1,980 @@
+package info.nightscout.pump.medtrum.services
+
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.os.Binder
+import android.os.IBinder
+import android.os.SystemClock
+import dagger.android.DaggerService
+import dagger.android.HasAndroidInjector
+import info.nightscout.core.utils.fabric.FabricPrivacy
+import info.nightscout.interfaces.constraints.Constraints
+import info.nightscout.interfaces.notifications.Notification
+import info.nightscout.interfaces.plugin.ActivePlugin
+import info.nightscout.interfaces.profile.Profile
+import info.nightscout.interfaces.profile.ProfileFunction
+import info.nightscout.interfaces.pump.DetailedBolusInfo
+import info.nightscout.interfaces.pump.DetailedBolusInfoStorage
+import info.nightscout.interfaces.pump.BolusProgressData
+import info.nightscout.interfaces.pump.PumpSync
+import info.nightscout.interfaces.pump.defs.PumpType
+import info.nightscout.interfaces.queue.Callback
+import info.nightscout.interfaces.queue.CommandQueue
+import info.nightscout.interfaces.ui.UiInteraction
+import info.nightscout.pump.medtrum.MedtrumPlugin
+import info.nightscout.pump.medtrum.MedtrumPump
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.code.ConnectionState
+import info.nightscout.pump.medtrum.comm.enums.AlarmState
+import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
+import info.nightscout.pump.medtrum.comm.packets.*
+import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
+import info.nightscout.rx.AapsSchedulers
+import info.nightscout.rx.bus.RxBus
+import info.nightscout.rx.events.EventAppExit
+import info.nightscout.rx.events.EventDismissNotification
+import info.nightscout.rx.events.EventOverviewBolusProgress
+import info.nightscout.rx.events.EventPreferenceChange
+import info.nightscout.rx.events.EventPumpStatusChanged
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.interfaces.ResourceHelper
+import info.nightscout.shared.sharedPreferences.SP
+import info.nightscout.shared.utils.DateUtil
+import info.nightscout.shared.utils.T
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+import kotlin.math.abs
+import kotlin.math.round
+
+class MedtrumService : DaggerService(), BLECommCallback {
+
+ @Inject lateinit var injector: HasAndroidInjector
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var aapsSchedulers: AapsSchedulers
+ @Inject lateinit var rxBus: RxBus
+ @Inject lateinit var sp: SP
+ @Inject lateinit var rh: ResourceHelper
+ @Inject lateinit var profileFunction: ProfileFunction
+ @Inject lateinit var commandQueue: CommandQueue
+ @Inject lateinit var context: Context
+ @Inject lateinit var medtrumPlugin: MedtrumPlugin
+ @Inject lateinit var medtrumPump: MedtrumPump
+ @Inject lateinit var activePlugin: ActivePlugin
+ @Inject lateinit var constraintChecker: Constraints
+ @Inject lateinit var uiInteraction: UiInteraction
+ @Inject lateinit var bleComm: BLEComm
+ @Inject lateinit var fabricPrivacy: FabricPrivacy
+ @Inject lateinit var pumpSync: PumpSync
+ @Inject lateinit var detailedBolusInfoStorage: DetailedBolusInfoStorage
+ @Inject lateinit var dateUtil: DateUtil
+
+ companion object {
+
+ private const val COMMAND_DEFAULT_TIMEOUT_SEC: Long = 60
+ private const val COMMAND_SYNC_TIMEOUT_SEC: Long = 120
+ private const val COMMAND_CONNECTING_TIMEOUT_SEC: Long = 30
+ private const val ALARM_HOURLY_MAX_CLEAR_CODE = 4
+ private const val ALARM_DAILY_MAX_CLEAR_CODE = 5
+ }
+
+ val timeUtil = MedtrumTimeUtil()
+
+ private val disposable = CompositeDisposable()
+ private val mBinder: IBinder = LocalBinder()
+
+ private var currentState: State = IdleState()
+ private var mPacket: MedtrumPacket? = null
+
+ private val scope = CoroutineScope(Dispatchers.Default)
+
+ val isConnected: Boolean
+ get() = medtrumPump.connectionState == ConnectionState.CONNECTED
+ val isConnecting: Boolean
+ get() = medtrumPump.connectionState == ConnectionState.CONNECTING
+
+ override fun onCreate() {
+ super.onCreate()
+ bleComm.setCallback(this)
+ disposable += rxBus
+ .toObservable(EventAppExit::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ stopSelf() }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventPreferenceChange::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ event ->
+ if (event.isChanged(rh.gs(R.string.key_sn_input))) {
+ aapsLogger.debug(LTag.PUMPCOMM, "Serial number changed, reporting new pump!")
+ pumpSync.connectNewPump()
+ medtrumPump.loadUserSettingsFromSP()
+ medtrumPump.setFakeTBRIfNeeded()
+ }
+ if (event.isChanged(rh.gs(R.string.key_alarm_setting))
+ || event.isChanged(rh.gs(R.string.key_patch_expiration))
+ || event.isChanged(rh.gs(R.string.key_hourly_max_insulin))
+ || event.isChanged(rh.gs(R.string.key_daily_max_insulin))
+ ) {
+ medtrumPump.loadUserSettingsFromSP()
+ commandQueue.setUserOptions(object : Callback() {
+ override fun run() {
+ if (medtrumPlugin.isInitialized() && this.result.success == false) {
+ uiInteraction.addNotification(
+ Notification.PUMP_SETTINGS_FAILED,
+ rh.gs(R.string.pump_setting_failed),
+ Notification.NORMAL,
+ )
+ }
+ }
+ })
+ }
+ }, fabricPrivacy::logException)
+ scope.launch {
+ medtrumPump.pumpStateFlow.collect { state ->
+ handlePumpStateUpdate(state)
+ }
+ }
+ scope.launch {
+ medtrumPump.connectionStateFlow.collect { state ->
+ if (medtrumPlugin.isInitialized()) {
+ when (state) {
+ ConnectionState.CONNECTED -> rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTED))
+ ConnectionState.DISCONNECTED -> rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED))
+ ConnectionState.CONNECTING -> rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTING))
+ ConnectionState.DISCONNECTING -> rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING))
+ }
+ }
+ }
+ }
+
+ medtrumPump.loadUserSettingsFromSP()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ disposable.clear()
+ scope.cancel()
+ }
+
+ fun connect(from: String): Boolean {
+ aapsLogger.debug(LTag.PUMP, "connect: called from: $from")
+ if (currentState is IdleState) {
+ medtrumPump.connectionState = ConnectionState.CONNECTING
+ return bleComm.connect(from, medtrumPump.pumpSN)
+ } else {
+ aapsLogger.error(LTag.PUMPCOMM, "Connect attempt when in non Idle state from: $from")
+ return false
+ }
+ }
+
+ fun startPrime(): Boolean {
+ return sendPacketAndGetResponse(PrimePacket(injector))
+ }
+
+ fun startActivate(): Boolean {
+ val profile = profileFunction.getProfile()?.let { medtrumPump.buildMedtrumProfileArray(it) }
+ val packet = profile?.let { ActivatePacket(injector, it) }
+ return packet?.let { sendPacketAndGetResponse(it) } == true
+ }
+
+ fun deactivatePatch(): Boolean {
+ var result = true
+ if (medtrumPump.tempBasalInProgress) {
+ result = sendPacketAndGetResponse(CancelTempBasalPacket(injector))
+ }
+ // Make sure we have all events of this patch if possible
+ loadEvents()
+ if (result) result = sendPacketAndGetResponse(StopPatchPacket(injector))
+ return result
+ }
+
+ fun stopConnecting() {
+ bleComm.disconnect("stopConnecting")
+ }
+
+ fun disconnect(from: String) {
+ medtrumPump.connectionState = ConnectionState.DISCONNECTING
+ bleComm.disconnect(from)
+ }
+
+ fun readPumpStatus() {
+ rxBus.send(EventPumpStatusChanged(rh.gs(R.string.gettingpumpstatus)))
+ updateTimeIfNeeded(false)
+ loadEvents()
+ }
+
+ fun timeUpdateNotification(updateSuccess: Boolean) {
+ if (updateSuccess) {
+ aapsLogger.debug(LTag.PUMPCOMM, "Pump time updated")
+ uiInteraction.addNotification(
+ Notification.INSIGHT_DATE_TIME_UPDATED, // :---)
+ rh.gs(info.nightscout.core.ui.R.string.pump_time_updated),
+ Notification.INFO,
+ )
+ } else {
+ aapsLogger.error(LTag.PUMPCOMM, "Failed to update pump time")
+ uiInteraction.addNotification(
+ Notification.PUMP_TIMEZONE_UPDATE_FAILED,
+ rh.gs(R.string.pump_time_update_failed),
+ Notification.URGENT,
+ )
+ }
+ }
+
+ fun updateTimeIfNeeded(needLoadHistory: Boolean = true): Boolean {
+ // Note we only check timeZone here, time is updated each connection attempt if needed, because the pump requires it to be checked
+ // But we dont check timeZone each time, therefore we do it here (if needed)
+ var result = true
+ if (medtrumPump.pumpTimeZoneOffset != dateUtil.getTimeZoneOffsetMinutes(dateUtil.now())) {
+ result = sendPacketAndGetResponse(SetTimePacket(injector))
+ if (result) result = sendPacketAndGetResponse(SetTimeZonePacket(injector))
+ timeUpdateNotification(result)
+ }
+ // Do this here, because TBR can be cancelled due to time change by connect flow
+ if (needLoadHistory) {
+ if (result) result = loadEvents()
+ }
+ if (result) medtrumPump.needCheckTimeUpdate = false
+ return result
+ }
+
+ fun loadEvents(): Boolean {
+ rxBus.send(EventPumpStatusChanged(rh.gs(R.string.gettingpumpstatus)))
+ // Sync records
+ val result = syncRecords()
+ if (result) {
+ aapsLogger.debug(LTag.PUMPCOMM, "Events loaded")
+ medtrumPump.lastConnection = System.currentTimeMillis()
+ } else {
+ aapsLogger.error(LTag.PUMPCOMM, "Failed to load events")
+ }
+ return result
+ }
+
+ fun clearAlarms(): Boolean {
+ var result = true
+ if (medtrumPump.pumpState in listOf(
+ MedtrumPumpState.PAUSED,
+ MedtrumPumpState.HMAX_SUSPENDED,
+ MedtrumPumpState.DMAX_SUSPENDED
+ )
+ ) {
+ when (medtrumPump.pumpState) {
+ MedtrumPumpState.HMAX_SUSPENDED -> {
+ result = sendPacketAndGetResponse(ClearPumpAlarmPacket(injector, ALARM_HOURLY_MAX_CLEAR_CODE))
+ }
+
+ MedtrumPumpState.DMAX_SUSPENDED -> {
+ result = sendPacketAndGetResponse(ClearPumpAlarmPacket(injector, ALARM_DAILY_MAX_CLEAR_CODE))
+ }
+
+ else -> {
+ // Nothing to reset
+ }
+ }
+ // Resume suspended pump
+ if (result) result = sendPacketAndGetResponse(ResumePumpPacket(injector))
+ }
+ return result
+ }
+
+ fun setUserSettings(): Boolean {
+ rxBus.send(EventPumpStatusChanged(rh.gs(R.string.settingpumpsettings)))
+ return sendPacketAndGetResponse(SetPatchPacket(injector))
+ }
+
+ fun setBolus(detailedBolusInfo: DetailedBolusInfo, t: EventOverviewBolusProgress.Treatment): Boolean {
+ if (!isConnected) return false
+ if (BolusProgressData.stopPressed) return false
+ val insulin = detailedBolusInfo.insulin
+ val bolusStart = System.currentTimeMillis()
+
+ medtrumPump.bolusDone = false
+ medtrumPump.bolusingTreatment = t
+ medtrumPump.bolusAmountToBeDelivered = insulin
+ medtrumPump.bolusStopped = false
+ medtrumPump.bolusProgressLastTimeStamp = bolusStart
+
+ if (insulin > 0 && !medtrumPump.bolusStopped) {
+ val result = sendPacketAndGetResponse(SetBolusPacket(injector, insulin))
+ if (result == false) {
+ aapsLogger.error(LTag.PUMPCOMM, "Failed to set bolus")
+ commandQueue.loadEvents(null) // make sure if anything is delivered (which is highly unlikely at this point) we get it
+ t.insulin = 0.0
+ return false
+ }
+ } else {
+ aapsLogger.debug(LTag.PUMPCOMM, "Bolus not set, insulin: $insulin, bolusStopped: ${medtrumPump.bolusStopped}")
+ t.insulin = 0.0
+ return false
+ }
+
+ detailedBolusInfo.timestamp = bolusStart // Make sure the timestamp is set to the start of the bolus
+ detailedBolusInfoStorage.add(detailedBolusInfo) // will be picked up on reading history
+ // Sync the initial bolus
+ val newRecord = pumpSync.addBolusWithTempId(
+ timestamp = detailedBolusInfo.timestamp,
+ amount = detailedBolusInfo.insulin,
+ temporaryId = detailedBolusInfo.timestamp,
+ type = detailedBolusInfo.bolusType,
+ pumpType = medtrumPump.pumpType(),
+ pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
+ )
+ if (newRecord) {
+ aapsLogger.debug(
+ LTag.PUMPCOMM,
+ "set bolus: **NEW** EVENT BOLUS (tempId) ${dateUtil.dateAndTimeString(detailedBolusInfo.timestamp)} (${detailedBolusInfo.timestamp}) Bolus: ${detailedBolusInfo.insulin}U "
+ )
+ } else {
+ aapsLogger.error(LTag.PUMPCOMM, "Bolus with tempId ${detailedBolusInfo.timestamp} already exists")
+ }
+
+ val bolusingEvent = EventOverviewBolusProgress
+ var communicationLost = false
+
+ while (!medtrumPump.bolusStopped && !medtrumPump.bolusDone && !communicationLost) {
+ SystemClock.sleep(100)
+ if (System.currentTimeMillis() - medtrumPump.bolusProgressLastTimeStamp > T.secs(20).msecs()) {
+ communicationLost = true
+ aapsLogger.warn(LTag.PUMPCOMM, "Communication stopped")
+ disconnect("Communication stopped")
+ } else {
+ bolusingEvent.t = medtrumPump.bolusingTreatment
+ bolusingEvent.status = rh.gs(info.nightscout.pump.common.R.string.bolus_delivered_so_far, medtrumPump.bolusingTreatment?.insulin, medtrumPump.bolusAmountToBeDelivered)
+ bolusingEvent.percent = round((medtrumPump.bolusingTreatment?.insulin?.div(medtrumPump.bolusAmountToBeDelivered) ?: 0.0) * 100).toInt() - 1
+ rxBus.send(bolusingEvent)
+ }
+ }
+
+ bolusingEvent.percent = 99
+ val bolusDurationInMSec = (insulin * 60 * 1000)
+ val expectedEnd = bolusStart + bolusDurationInMSec + 2000
+ while (System.currentTimeMillis() < expectedEnd && !medtrumPump.bolusDone) {
+ SystemClock.sleep(1000)
+ }
+
+ bolusingEvent.t = medtrumPump.bolusingTreatment
+ medtrumPump.bolusingTreatment = null
+
+ if (medtrumPump.bolusStopped && t.insulin == 0.0) {
+ // In this case we don't get a bolus end event, so need to remove all the stuff added previously
+ val syncOk = pumpSync.syncBolusWithTempId(
+ timestamp = bolusStart,
+ amount = 0.0,
+ temporaryId = bolusStart,
+ type = detailedBolusInfo.bolusType,
+ pumpId = bolusStart,
+ pumpType = medtrumPump.pumpType(),
+ pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
+ )
+ aapsLogger.debug(
+ LTag.PUMPCOMM,
+ "set bolus: **SYNC** EVENT BOLUS (tempId) ${dateUtil.dateAndTimeString(detailedBolusInfo.timestamp)} (${bolusStart}) Bolus: ${0.0}U SyncOK: $syncOk"
+ )
+ // remove detailed bolus info
+ detailedBolusInfoStorage.findDetailedBolusInfo(bolusStart, detailedBolusInfo.insulin)
+ }
+
+ // Do not call update status directly, reconnection may be needed
+ commandQueue.loadEvents(object : Callback() {
+ override fun run() {
+ rxBus.send(EventPumpStatusChanged(rh.gs(R.string.gettingbolusstatus)))
+ bolusingEvent.percent = 100
+ }
+ })
+ return true
+ }
+
+ fun stopBolus() {
+ aapsLogger.debug(LTag.PUMPCOMM, "bolusStop >>>>> @ " + if (medtrumPump.bolusingTreatment == null) "" else medtrumPump.bolusingTreatment?.insulin)
+ if (isConnected) {
+ var success = sendPacketAndGetResponse(CancelBolusPacket(injector))
+ var timeout = System.currentTimeMillis() + T.secs(30).msecs()
+ while (!success && System.currentTimeMillis() < timeout) {
+ success = sendPacketAndGetResponse(CancelBolusPacket(injector))
+ SystemClock.sleep(200)
+ }
+ aapsLogger.debug(LTag.PUMPCOMM, "bolusStop success: $success")
+ medtrumPump.bolusStopped = true
+ } else {
+ medtrumPump.bolusStopped = true
+ }
+ }
+
+ fun setTempBasal(absoluteRate: Double, durationInMinutes: Int): Boolean {
+ var result = true
+ if (medtrumPump.tempBasalInProgress) {
+ result = sendPacketAndGetResponse(CancelTempBasalPacket(injector))
+ }
+ if (result) result = sendPacketAndGetResponse(SetTempBasalPacket(injector, absoluteRate, durationInMinutes))
+
+ // Get history records, this will update the prevoius basals
+ // Do not call update status directly, reconnection may be needed
+ commandQueue.loadEvents(object : Callback() {
+ override fun run() {
+ rxBus.send(EventPumpStatusChanged(rh.gs(R.string.gettingtempbasalstatus)))
+ }
+ })
+
+ return result
+ }
+
+ fun cancelTempBasal(): Boolean {
+ var result = sendPacketAndGetResponse(CancelTempBasalPacket(injector))
+
+ // Get history records, this will update the prevoius basals
+ // Do not call update status directly, reconnection may be needed
+ commandQueue.loadEvents(object : Callback() {
+ override fun run() {
+ rxBus.send(EventPumpStatusChanged(rh.gs(R.string.gettingtempbasalstatus)))
+ }
+ })
+
+ return result
+ }
+
+ fun updateBasalsInPump(profile: Profile): Boolean {
+ var result = true
+ // Update basal affects the TBR records (the pump will cancel the TBR, set our basal profile, and resume the TBR in a new record)
+ // Cancel any TBR in progress
+ if (medtrumPump.tempBasalInProgress) {
+ result = sendPacketAndGetResponse(CancelTempBasalPacket(injector))
+ }
+ val packet = medtrumPump.buildMedtrumProfileArray(profile)?.let { SetBasalProfilePacket(injector, it) }
+ if (result) result = packet?.let { sendPacketAndGetResponse(it) } == true
+
+ // Get history records, this will update the pump state and add changes in TBR to AAPS history
+ commandQueue.loadEvents(null)
+
+ return result
+ }
+
+ /** This gets the history records from the pump */
+ private fun syncRecords(): Boolean {
+ aapsLogger.debug(LTag.PUMP, "syncRecords: called!, syncedSequenceNumber: ${medtrumPump.syncedSequenceNumber}, currentSequenceNumber: ${medtrumPump.currentSequenceNumber}")
+ var result = true
+ // Note: medtrum app fetches all records when they sync?
+ if (medtrumPump.syncedSequenceNumber < medtrumPump.currentSequenceNumber) {
+ for (sequence in (medtrumPump.syncedSequenceNumber + 1)..medtrumPump.currentSequenceNumber) {
+ result = sendPacketAndGetResponse(GetRecordPacket(injector, sequence), COMMAND_SYNC_TIMEOUT_SEC)
+ SystemClock.sleep(100)
+ if (result == false) break
+ }
+ }
+ return result
+ }
+
+ private fun handlePumpStateUpdate(state: MedtrumPumpState) {
+ // Map the pump state to an alarm state and add it to the active alarms
+ val alarmState = when (state) {
+ MedtrumPumpState.NONE -> AlarmState.NONE
+ MedtrumPumpState.LOWBG_SUSPENDED -> AlarmState.LOWBG_SUSPENDED
+ MedtrumPumpState.LOWBG_SUSPENDED2 -> AlarmState.LOWBG_SUSPENDED2
+ MedtrumPumpState.AUTO_SUSPENDED -> AlarmState.AUTO_SUSPENDED
+ MedtrumPumpState.HMAX_SUSPENDED -> AlarmState.HMAX_SUSPENDED
+ MedtrumPumpState.DMAX_SUSPENDED -> AlarmState.DMAX_SUSPENDED
+ MedtrumPumpState.SUSPENDED -> AlarmState.SUSPENDED
+ MedtrumPumpState.PAUSED -> AlarmState.PAUSED
+ MedtrumPumpState.OCCLUSION -> AlarmState.OCCLUSION
+ MedtrumPumpState.EXPIRED -> AlarmState.EXPIRED
+ MedtrumPumpState.RESERVOIR_EMPTY -> AlarmState.RESERVOIR_EMPTY
+ MedtrumPumpState.PATCH_FAULT -> AlarmState.PATCH_FAULT
+ MedtrumPumpState.PATCH_FAULT2 -> AlarmState.PATCH_FAULT2
+ MedtrumPumpState.BASE_FAULT -> AlarmState.BASE_FAULT
+ MedtrumPumpState.BATTERY_OUT -> AlarmState.BATTERY_OUT
+ MedtrumPumpState.NO_CALIBRATION -> AlarmState.NO_CALIBRATION
+ else -> null
+ }
+ if (alarmState != null && alarmState != AlarmState.NONE) {
+ medtrumPump.addAlarm(alarmState)
+ pumpSync.insertAnnouncement(
+ medtrumPump.alarmStateToString(alarmState),
+ null,
+ medtrumPump.pumpType(),
+ medtrumPump.pumpSN.toString(radix = 16)
+ )
+ }
+
+ // Map the pump state to a notification
+ when (state) {
+ MedtrumPumpState.NONE,
+ MedtrumPumpState.STOPPED -> {
+ rxBus.send(EventDismissNotification(Notification.PUMP_ERROR))
+ rxBus.send(EventDismissNotification(Notification.PUMP_SUSPENDED))
+ uiInteraction.addNotification(
+ Notification.PATCH_NOT_ACTIVE,
+ rh.gs(R.string.patch_not_active),
+ Notification.URGENT,
+ )
+ medtrumPump.setFakeTBRIfNeeded()
+ medtrumPump.clearAlarmState()
+ }
+
+ MedtrumPumpState.IDLE,
+ MedtrumPumpState.FILLED,
+ MedtrumPumpState.PRIMING,
+ MedtrumPumpState.PRIMED,
+ MedtrumPumpState.EJECTING,
+ MedtrumPumpState.EJECTED -> {
+ rxBus.send(EventDismissNotification(Notification.PUMP_ERROR))
+ rxBus.send(EventDismissNotification(Notification.PUMP_SUSPENDED))
+ medtrumPump.setFakeTBRIfNeeded()
+ medtrumPump.clearAlarmState()
+ }
+
+ MedtrumPumpState.ACTIVE,
+ MedtrumPumpState.ACTIVE_ALT -> {
+ rxBus.send(EventDismissNotification(Notification.PATCH_NOT_ACTIVE))
+ rxBus.send(EventDismissNotification(Notification.PUMP_SUSPENDED))
+ medtrumPump.clearAlarmState()
+ }
+
+ MedtrumPumpState.LOWBG_SUSPENDED,
+ MedtrumPumpState.LOWBG_SUSPENDED2,
+ MedtrumPumpState.AUTO_SUSPENDED,
+ MedtrumPumpState.SUSPENDED,
+ MedtrumPumpState.PAUSED -> {
+ uiInteraction.addNotification(
+ Notification.PUMP_SUSPENDED,
+ rh.gs(R.string.pump_is_suspended),
+ Notification.NORMAL,
+ )
+ // Pump will report proper TBR for this
+ }
+
+ MedtrumPumpState.HMAX_SUSPENDED -> {
+ uiInteraction.addNotification(
+ Notification.PUMP_SUSPENDED,
+ rh.gs(R.string.pump_is_suspended_hour_max),
+ Notification.NORMAL,
+ )
+ // Pump will report proper TBR for this
+ }
+
+ MedtrumPumpState.DMAX_SUSPENDED -> {
+ uiInteraction.addNotification(
+ Notification.PUMP_SUSPENDED,
+ rh.gs(R.string.pump_is_suspended_day_max),
+ Notification.NORMAL,
+ )
+ // Pump will report proper TBR for this
+ }
+
+ MedtrumPumpState.OCCLUSION,
+ MedtrumPumpState.EXPIRED,
+ MedtrumPumpState.RESERVOIR_EMPTY,
+ MedtrumPumpState.PATCH_FAULT,
+ MedtrumPumpState.PATCH_FAULT2,
+ MedtrumPumpState.BASE_FAULT,
+ MedtrumPumpState.BATTERY_OUT,
+ MedtrumPumpState.NO_CALIBRATION -> {
+ rxBus.send(EventDismissNotification(Notification.PATCH_NOT_ACTIVE))
+ rxBus.send(EventDismissNotification(Notification.PUMP_SUSPENDED))
+ // Pump suspended due to error, show error!
+ uiInteraction.addNotificationWithSound(
+ Notification.PUMP_ERROR,
+ rh.gs(R.string.pump_error, alarmState?.let { medtrumPump.alarmStateToString(it) }),
+ Notification.URGENT,
+ info.nightscout.core.ui.R.raw.alarm
+ )
+ medtrumPump.setFakeTBRIfNeeded()
+ }
+ }
+ }
+
+ /** BLECommCallbacks */
+ override fun onBLEConnected() {
+ aapsLogger.debug(LTag.PUMPCOMM, "<<<<< onBLEConnected")
+ currentState.onConnected()
+ }
+
+ override fun onBLEDisconnected() {
+ aapsLogger.debug(LTag.PUMPCOMM, "<<<<< onBLEDisconnected")
+ currentState.onDisconnected()
+ }
+
+ override fun onNotification(notification: ByteArray) {
+ aapsLogger.debug(LTag.PUMPCOMM, "<<<<< onNotification" + notification.contentToString())
+ NotificationPacket(injector).handleNotification(notification)
+ }
+
+ override fun onIndication(indication: ByteArray) {
+ aapsLogger.debug(LTag.PUMPCOMM, "<<<<< onIndication" + indication.contentToString())
+ currentState.onIndication(indication)
+ }
+
+ override fun onSendMessageError(reason: String) {
+ aapsLogger.debug(LTag.PUMPCOMM, "<<<<< error during send message $reason")
+ currentState.onSendMessageError(reason)
+ }
+
+ /** Service stuff */
+ inner class LocalBinder : Binder() {
+
+ val serviceInstance: MedtrumService
+ get() = this@MedtrumService
+ }
+
+ override fun onBind(intent: Intent): IBinder {
+ return mBinder
+ }
+
+ override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+ return Service.START_STICKY
+ }
+
+ /**
+ * States are used to keep track of the communication and to guide the flow
+ */
+ private fun toState(nextState: State) {
+ currentState = nextState
+ currentState.onEnter()
+ }
+
+ private fun sendPacketAndGetResponse(packet: MedtrumPacket, timeout: Long = COMMAND_DEFAULT_TIMEOUT_SEC): Boolean {
+ var result = false
+ if (currentState is ReadyState) {
+ toState(CommandState())
+ mPacket = packet
+ mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
+ result = currentState.waitForResponse(timeout)
+ } else {
+ aapsLogger.error(LTag.PUMPCOMM, "Send packet attempt when in non Ready state")
+ }
+ return result
+ }
+
+ // State class
+ private abstract inner class State {
+
+ protected var responseHandled = false
+ protected var responseSuccess = false
+ protected var sendRetryCounter = 0
+
+ open fun onEnter() {}
+ open fun onIndication(data: ByteArray) {
+ aapsLogger.warn(LTag.PUMPCOMM, "onIndication: " + this.toString() + "Should not be called here!")
+ }
+
+ open fun onConnected() {
+ aapsLogger.debug(LTag.PUMPCOMM, "onConnected")
+ }
+
+ fun onDisconnected() {
+ aapsLogger.debug(LTag.PUMPCOMM, "onDisconnected")
+ medtrumPump.connectionState = ConnectionState.DISCONNECTED
+ responseHandled = true
+ responseSuccess = false
+ toState(IdleState())
+ }
+
+ fun waitForResponse(timeout: Long): Boolean {
+ val startTime = System.currentTimeMillis()
+ val timeoutMillis = T.secs(timeout).msecs()
+ while (!responseHandled) {
+ if (System.currentTimeMillis() - startTime > timeoutMillis) {
+ // If we haven't received a response in the specified time, assume the command failed
+ aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service State timeout")
+ // Disconnect to cancel any outstanding commands and go back to ready state
+ disconnect("Timeout")
+ toState(IdleState())
+ return false
+ }
+ SystemClock.sleep(25)
+ }
+ aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service State responseHandled: $responseHandled responseSuccess: $responseSuccess")
+ return responseSuccess
+ }
+
+ fun onSendMessageError(reason: String) {
+ aapsLogger.warn(LTag.PUMPCOMM, "onSendMessageError: " + this.toString() + "reason: $reason")
+ // Retry 3 times
+ if (sendRetryCounter < 3) {
+ sendRetryCounter++
+ mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
+ } else {
+ responseHandled = true
+ responseSuccess = false
+ disconnect("onSendMessageError")
+ toState(IdleState())
+ }
+ }
+ }
+
+ private inner class IdleState : State() {
+
+ override fun onEnter() {
+ aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached IdleState")
+ }
+
+ override fun onConnected() {
+ super.onConnected()
+ toState(AuthState())
+ }
+ }
+
+ // State for connect flow
+ private inner class AuthState : State() {
+
+ val retryCounter = 0
+
+ override fun onEnter() {
+ aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached AuthState")
+ mPacket = AuthorizePacket(injector)
+ mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
+ scope.launch {
+ waitForResponse(COMMAND_CONNECTING_TIMEOUT_SEC)
+ }
+ }
+
+ override fun onIndication(data: ByteArray) {
+ if (mPacket?.handleResponse(data) == true) {
+ // Succes!
+ responseHandled = true
+ responseSuccess = true
+ // Check if we have a supported pump
+ if (medtrumPump.pumpType() == PumpType.MEDTRUM_UNTESTED) {
+ // Throw error
+ aapsLogger.error(LTag.PUMPCOMM, "Unsupported pump type")
+ uiInteraction.addNotificationWithSound(
+ Notification.PUMP_ERROR,
+ rh.gs(R.string.pump_unsupported, medtrumPump.deviceType),
+ Notification.URGENT,
+ info.nightscout.core.ui.R.raw.alarm
+ )
+ disconnect("Unsupported pump")
+ toState(IdleState())
+ } else {
+ toState(GetDeviceTypeState())
+ }
+ } else if (mPacket?.failed == true) {
+ // Failure
+ responseHandled = true
+ responseSuccess = false
+ disconnect("Failure")
+ toState(IdleState())
+ }
+ }
+ }
+
+ // State for connect flow
+ private inner class GetDeviceTypeState : State() {
+
+ override fun onEnter() {
+ aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached GetDeviceTypeState")
+ mPacket = GetDeviceTypePacket(injector)
+ mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
+ scope.launch {
+ waitForResponse(COMMAND_CONNECTING_TIMEOUT_SEC)
+ }
+ }
+
+ override fun onIndication(data: ByteArray) {
+ if (mPacket?.handleResponse(data) == true) {
+ // Succes!
+ responseHandled = true
+ responseSuccess = true
+ // Place holder, not really used (yet)
+ val deviceType = (mPacket as GetDeviceTypePacket).deviceType
+ val deviceSN = (mPacket as GetDeviceTypePacket).deviceSN
+ aapsLogger.debug(LTag.PUMPCOMM, "GetDeviceTypeState: deviceType: $deviceType deviceSN: $deviceSN")
+ toState(GetTimeState())
+ } else if (mPacket?.failed == true) {
+ // Failure
+ responseHandled = true
+ responseSuccess = false
+ disconnect("Failure")
+ toState(IdleState())
+ }
+ }
+ }
+
+ // State for connect flow
+ private inner class GetTimeState : State() {
+
+ override fun onEnter() {
+ aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached GetTimeState")
+ mPacket = GetTimePacket(injector)
+ mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
+ scope.launch {
+ waitForResponse(COMMAND_CONNECTING_TIMEOUT_SEC)
+ }
+ }
+
+ override fun onIndication(data: ByteArray) {
+ if (mPacket?.handleResponse(data) == true) {
+ // Succes!
+ responseHandled = true
+ responseSuccess = true
+ val currTime = dateUtil.now()
+ aapsLogger.debug(LTag.PUMPCOMM, "GetTimeState.onIndication systemTime: $currTime, pumpTime: ${medtrumPump.lastTimeReceivedFromPump}")
+ if (abs(medtrumPump.lastTimeReceivedFromPump - currTime) <= T.secs(10).msecs()) { // Allow 10 sec deviation
+ toState(SynchronizeState())
+ } else {
+ aapsLogger.warn(LTag.PUMPCOMM, "GetTimeState.onIndication time difference too big, setting time")
+ toState(SetTimeState())
+ }
+ } else if (mPacket?.failed == true) {
+ // Failure
+ responseHandled = true
+ responseSuccess = false
+ disconnect("Failure")
+ toState(IdleState())
+ }
+ }
+ }
+
+ // State for connect flow
+ private inner class SetTimeState : State() {
+
+ override fun onEnter() {
+ aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached SetTimeState")
+ mPacket = SetTimePacket(injector)
+ mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
+ scope.launch {
+ waitForResponse(COMMAND_CONNECTING_TIMEOUT_SEC)
+ }
+ }
+
+ override fun onIndication(data: ByteArray) {
+ if (mPacket?.handleResponse(data) == true) {
+ // Succes!
+ responseHandled = true
+ responseSuccess = true
+ toState(SetTimeZoneState())
+ } else if (mPacket?.failed == true) {
+ // Failure
+ responseHandled = true
+ responseSuccess = false
+ disconnect("Failure")
+ toState(IdleState())
+ }
+ }
+ }
+
+ // State for connect flow
+ private inner class SetTimeZoneState : State() {
+
+ override fun onEnter() {
+ aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached SetTimeZoneState")
+ mPacket = SetTimeZonePacket(injector)
+ mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
+ scope.launch {
+ waitForResponse(COMMAND_CONNECTING_TIMEOUT_SEC)
+ }
+ }
+
+ override fun onIndication(data: ByteArray) {
+ if (mPacket?.handleResponse(data) == true) {
+ // Succes!
+ responseHandled = true
+ responseSuccess = true
+ medtrumPump.needCheckTimeUpdate = false
+ timeUpdateNotification(true)
+ toState(SynchronizeState())
+ } else if (mPacket?.failed == true) {
+ // Failure
+ responseHandled = true
+ responseSuccess = false
+ disconnect("Failure")
+ toState(IdleState())
+ }
+ }
+ }
+
+ // State for connect flow
+ private inner class SynchronizeState : State() {
+
+ override fun onEnter() {
+ aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached SynchronizeState")
+ mPacket = SynchronizePacket(injector)
+ mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
+ scope.launch {
+ waitForResponse(COMMAND_CONNECTING_TIMEOUT_SEC)
+ }
+ }
+
+ override fun onIndication(data: ByteArray) {
+ if (mPacket?.handleResponse(data) == true) {
+ // Succes!
+ responseHandled = true
+ responseSuccess = true
+ toState(SubscribeState())
+ } else if (mPacket?.failed == true) {
+ // Failure
+ responseHandled = true
+ responseSuccess = false
+ disconnect("Failure")
+ toState(IdleState())
+ }
+ }
+ }
+
+ // State for connect flow
+ private inner class SubscribeState : State() {
+
+ override fun onEnter() {
+ aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached SubscribeState")
+ mPacket = SubscribePacket(injector)
+ mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
+ scope.launch {
+ waitForResponse(COMMAND_CONNECTING_TIMEOUT_SEC)
+ }
+ }
+
+ override fun onIndication(data: ByteArray) {
+ if (mPacket?.handleResponse(data) == true) {
+ // Succes!
+ responseHandled = true
+ responseSuccess = true
+ toState(ReadyState())
+ } else if (mPacket?.failed == true) {
+ // Failure
+ responseHandled = true
+ responseSuccess = false
+ disconnect("Failure")
+ toState(IdleState())
+ }
+ }
+ }
+
+ // This state is reached when the patch is ready to receive commands (Activation, Bolus, temp basal and whatever)
+ private inner class ReadyState : State() {
+
+ override fun onEnter() {
+ aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached ReadyState!")
+ // Now we are fully connected and authenticated and we can start sending commands. Let AAPS know
+ if (isConnected == false) {
+ medtrumPump.connectionState = ConnectionState.CONNECTED
+ }
+ }
+ }
+
+ // This state is when a command is send and we wait for a response for that command
+ private inner class CommandState : State() {
+
+ override fun onEnter() {
+ aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached CommandState")
+ }
+
+ override fun onIndication(data: ByteArray) {
+ if (mPacket?.handleResponse(data) == true) {
+ // Succes!
+ responseHandled = true
+ responseSuccess = true
+ toState(ReadyState())
+ } else if (mPacket?.failed == true) {
+ // Failure
+ responseHandled = true
+ responseSuccess = false
+ toState(ReadyState())
+ }
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumActivateCompleteFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumActivateCompleteFragment.kt
new file mode 100644
index 0000000000..b714622053
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumActivateCompleteFragment.kt
@@ -0,0 +1,47 @@
+package info.nightscout.pump.medtrum.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.core.ui.toast.ToastUtils
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.databinding.FragmentMedtrumActivateCompleteBinding
+import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.interfaces.ResourceHelper
+import javax.inject.Inject
+
+class MedtrumActivateCompleteFragment : MedtrumBaseFragment() {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var rh: ResourceHelper
+
+ companion object {
+
+ fun newInstance(): MedtrumActivateCompleteFragment = MedtrumActivateCompleteFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_medtrum_activate_complete
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
+ viewModel?.apply {
+ setupStep.observe(viewLifecycleOwner) {
+ when (it) {
+ MedtrumViewModel.SetupStep.INITIAL,
+ MedtrumViewModel.SetupStep.PRIMED -> Unit // Nothing to do here, previous state
+ MedtrumViewModel.SetupStep.ACTIVATED -> btnPositive.visibility = View.VISIBLE
+
+ else -> {
+ ToastUtils.errorToast(requireContext(), rh.gs(R.string.unexpected_state, it.toString()))
+ aapsLogger.error(LTag.PUMP, "Unexpected state: $it")
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumActivateFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumActivateFragment.kt
new file mode 100644
index 0000000000..0654dbfa64
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumActivateFragment.kt
@@ -0,0 +1,64 @@
+package info.nightscout.pump.medtrum.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.core.ui.dialogs.OKDialog
+import info.nightscout.core.ui.toast.ToastUtils
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.code.PatchStep
+import info.nightscout.pump.medtrum.databinding.FragmentMedtrumActivateBinding
+import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.interfaces.ResourceHelper
+import javax.inject.Inject
+
+class MedtrumActivateFragment : MedtrumBaseFragment() {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var rh: ResourceHelper
+
+ companion object {
+
+ fun newInstance(): MedtrumActivateFragment = MedtrumActivateFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_medtrum_activate
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
+ viewModel?.apply {
+ setupStep.observe(viewLifecycleOwner) {
+ when (it) {
+ MedtrumViewModel.SetupStep.INITIAL,
+ MedtrumViewModel.SetupStep.PRIMED -> Unit // Nothing to do here, previous state
+ MedtrumViewModel.SetupStep.ACTIVATED -> moveStep(PatchStep.ACTIVATE_COMPLETE)
+
+ MedtrumViewModel.SetupStep.ERROR -> {
+ moveStep(PatchStep.ERROR)
+ updateSetupStep(MedtrumViewModel.SetupStep.PRIMED) // Reset setup step
+ binding.textActivatingPump.text = rh.gs(R.string.activating_error)
+ binding.btnPositive.visibility = View.VISIBLE
+ }
+
+ else -> {
+ ToastUtils.errorToast(requireContext(), "Unexpected state: $it")
+ aapsLogger.error(LTag.PUMP, "Unexpected state: $it")
+ }
+ }
+ }
+ startActivate()
+ }
+ btnNegative.setOnClickListener {
+ OKDialog.showConfirmation(requireActivity(), rh.gs(R.string.cancel_sure)) {
+ viewModel?.apply {
+ moveStep(PatchStep.CANCEL)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumActivity.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumActivity.kt
new file mode 100644
index 0000000000..07f96164d8
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumActivity.kt
@@ -0,0 +1,111 @@
+package info.nightscout.pump.medtrum.ui
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.os.Bundle
+import android.view.MotionEvent
+import android.view.WindowManager
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.core.utils.extensions.safeGetSerializableExtra
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.code.PatchStep
+import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
+import info.nightscout.pump.medtrum.databinding.ActivityMedtrumBinding
+import info.nightscout.pump.medtrum.extension.replaceFragmentInActivity
+import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
+
+class MedtrumActivity : MedtrumBaseActivity() {
+
+ override fun getLayoutId(): Int = R.layout.activity_medtrum
+
+ override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+ return super.dispatchTouchEvent(event)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+
+ binding.apply {
+ viewModel = ViewModelProvider(this@MedtrumActivity, viewModelFactory).get(MedtrumViewModel::class.java)
+ viewModel?.apply {
+ processIntent(intent)
+
+ patchStep.observe(this@MedtrumActivity) {
+ when (it) {
+ PatchStep.PREPARE_PATCH -> setupViewFragment(MedtrumPreparePatchFragment.newInstance())
+ PatchStep.PREPARE_PATCH_CONNECT -> setupViewFragment(MedtrumPreparePatchConnectFragment.newInstance())
+ PatchStep.PRIME -> setupViewFragment(MedtrumPrimeFragment.newInstance())
+ PatchStep.PRIMING -> setupViewFragment(MedtrumPrimingFragment.newInstance())
+ PatchStep.PRIME_COMPLETE -> setupViewFragment(MedtrumPrimeCompleteFragment.newInstance())
+ PatchStep.ATTACH_PATCH -> setupViewFragment(MedtrumAttachPatchFragment.newInstance())
+ PatchStep.ACTIVATE -> setupViewFragment(MedtrumActivateFragment.newInstance())
+ PatchStep.ACTIVATE_COMPLETE -> setupViewFragment(MedtrumActivateCompleteFragment.newInstance())
+ PatchStep.CANCEL,
+ PatchStep.COMPLETE -> this@MedtrumActivity.finish()
+ PatchStep.ERROR -> Unit // Do nothing, let activity handle this
+ PatchStep.RETRY_ACTIVATION -> setupViewFragment(MedtrumRetryActivationFragment.newInstance())
+ PatchStep.RETRY_ACTIVATION_CONNECT -> setupViewFragment(MedtrumRetryActivationConnectFragment.newInstance())
+ PatchStep.START_DEACTIVATION -> setupViewFragment(MedtrumStartDeactivationFragment.newInstance())
+ PatchStep.DEACTIVATE -> setupViewFragment(MedtrumDeactivatePatchFragment.newInstance())
+
+ PatchStep.FORCE_DEACTIVATION -> {
+ medtrumPump.pumpState = MedtrumPumpState.STOPPED
+ moveStep(PatchStep.DEACTIVATION_COMPLETE)
+ }
+
+ PatchStep.DEACTIVATION_COMPLETE -> setupViewFragment(MedtrumDeactivationCompleteFragment.newInstance())
+ null -> Unit
+ }
+ }
+ }
+ }
+ }
+
+ override fun onNewIntent(intent: Intent?) {
+ super.onNewIntent(intent)
+ processIntent(intent)
+ }
+
+ private fun processIntent(intent: Intent?) {
+ binding.viewModel?.apply {
+ intent?.run {
+ val step = intent.safeGetSerializableExtra(EXTRA_START_PATCH_STEP, PatchStep::class.java)
+ if (step != null) {
+ initializePatchStep(step)
+ }
+ }
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ }
+
+ override fun onBackPressed() {
+ binding.viewModel?.apply {
+ // Do nothing
+ }
+ }
+
+ companion object {
+
+ const val EXTRA_START_PATCH_STEP = "EXTRA_START_PATCH_FRAGMENT_UI"
+ const val EXTRA_START_FROM_MENU = "EXTRA_START_FROM_MENU"
+
+ @JvmStatic fun createIntentFromMenu(context: Context, patchStep: PatchStep): Intent {
+ return Intent(context, MedtrumActivity::class.java).apply {
+ putExtra(EXTRA_START_PATCH_STEP, patchStep)
+ putExtra(EXTRA_START_FROM_MENU, true)
+ }
+ }
+
+ }
+
+ private fun setupViewFragment(baseFragment: MedtrumBaseFragment<*>) {
+ replaceFragmentInActivity(baseFragment, R.id.framelayout_fragment, false)
+ }
+
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumAttachPatchFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumAttachPatchFragment.kt
new file mode 100644
index 0000000000..524f6d8139
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumAttachPatchFragment.kt
@@ -0,0 +1,56 @@
+package info.nightscout.pump.medtrum.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.core.ui.dialogs.OKDialog
+import info.nightscout.core.ui.toast.ToastUtils
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.code.PatchStep
+import info.nightscout.pump.medtrum.databinding.FragmentMedtrumAttachPatchBinding
+import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.interfaces.ResourceHelper
+import javax.inject.Inject
+
+class MedtrumAttachPatchFragment : MedtrumBaseFragment() {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var rh: ResourceHelper
+
+ companion object {
+
+ fun newInstance(): MedtrumAttachPatchFragment = MedtrumAttachPatchFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_medtrum_attach_patch
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ aapsLogger.debug(LTag.PUMP, "MedtrumAttachPatchFragment onViewCreated")
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
+ viewModel?.apply {
+ setupStep.observe(viewLifecycleOwner) {
+ when (it) {
+ MedtrumViewModel.SetupStep.INITIAL,
+ MedtrumViewModel.SetupStep.PRIMED -> Unit // Nothing to do here, previous state
+
+ else -> {
+ ToastUtils.errorToast(requireContext(), rh.gs(R.string.unexpected_state, it.toString()))
+ aapsLogger.error(LTag.PUMP, "Unexpected state: $it")
+ }
+ }
+ }
+ }
+ btnNegative.setOnClickListener {
+ OKDialog.showConfirmation(requireActivity(), rh.gs(R.string.cancel_sure)) {
+ viewModel?.apply {
+ moveStep(PatchStep.CANCEL)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumBaseActivity.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumBaseActivity.kt
new file mode 100644
index 0000000000..110a18513f
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumBaseActivity.kt
@@ -0,0 +1,57 @@
+package info.nightscout.pump.medtrum.ui
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.result.ActivityResultLauncher
+import androidx.annotation.LayoutRes
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ViewDataBinding
+import androidx.lifecycle.ViewModelProvider
+import dagger.android.support.DaggerAppCompatActivity
+import info.nightscout.core.ui.R
+import info.nightscout.pump.medtrum.di.MedtrumPluginQualifier
+import info.nightscout.rx.AapsSchedulers
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import javax.inject.Inject
+
+abstract class MedtrumBaseActivity : DaggerAppCompatActivity(), MedtrumBaseNavigator {
+ @Inject
+ @MedtrumPluginQualifier
+ lateinit var viewModelFactory: ViewModelProvider.Factory
+
+ @Inject lateinit var aapsSchedulers: AapsSchedulers
+
+ protected lateinit var binding: B
+
+ private val compositeDisposable = CompositeDisposable()
+
+ protected lateinit var getResult: ActivityResultLauncher
+
+ @LayoutRes
+ abstract fun getLayoutId(): Int
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setTheme(R.style.AppTheme_NoActionBar)
+
+ binding = DataBindingUtil.setContentView(this, getLayoutId())
+ binding.lifecycleOwner = this
+
+ }
+
+ override fun back() {
+ if(supportFragmentManager.backStackEntryCount == 0) {
+ finish()
+ } else {
+ supportFragmentManager.popBackStack()
+ }
+ }
+
+ override fun finish(finishAffinity: Boolean) {
+ if(finishAffinity) {
+ finishAffinity()
+ } else {
+ finish()
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumBaseFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumBaseFragment.kt
new file mode 100644
index 0000000000..1add056d09
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumBaseFragment.kt
@@ -0,0 +1,66 @@
+package info.nightscout.pump.medtrum.ui
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.CallSuper
+import androidx.annotation.LayoutRes
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ViewDataBinding
+import androidx.lifecycle.ViewModelProvider
+import dagger.android.support.DaggerFragment
+import info.nightscout.pump.medtrum.di.MedtrumPluginQualifier
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import javax.inject.Inject
+
+abstract class MedtrumBaseFragment : DaggerFragment(), MedtrumBaseNavigator {
+ @Inject
+ @MedtrumPluginQualifier
+ lateinit var viewModelFactory: ViewModelProvider.Factory
+
+ protected var baseActivity: MedtrumBaseActivity<*>? = null
+
+ protected lateinit var binding: B
+
+ private val compositeDisposable = CompositeDisposable()
+
+ @LayoutRes
+ abstract fun getLayoutId(): Int
+
+ @CallSuper
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ if (context is MedtrumBaseActivity<*>) {
+ baseActivity = context
+ }
+ }
+
+ @CallSuper
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ binding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+ return binding.root
+ }
+
+ @CallSuper
+ override fun onDestroy() {
+ super.onDestroy()
+ compositeDisposable.dispose()
+ }
+
+ @CallSuper
+ override fun onDetach() {
+ super.onDetach()
+ baseActivity = null
+ }
+
+ override fun back() {
+ baseActivity?.back()
+ }
+
+ override fun finish(finishAffinity: Boolean) {
+ baseActivity?.finish(finishAffinity)
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumBaseNavigator.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumBaseNavigator.kt
new file mode 100644
index 0000000000..5e2cb70651
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumBaseNavigator.kt
@@ -0,0 +1,7 @@
+package info.nightscout.pump.medtrum.ui
+
+interface MedtrumBaseNavigator {
+ fun back()
+
+ fun finish(finishAffinity: Boolean = false)
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumDeactivatePatchFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumDeactivatePatchFragment.kt
new file mode 100644
index 0000000000..ec94ef89f1
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumDeactivatePatchFragment.kt
@@ -0,0 +1,62 @@
+package info.nightscout.pump.medtrum.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.core.ui.dialogs.OKDialog
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.code.PatchStep
+import info.nightscout.pump.medtrum.databinding.FragmentMedtrumDeactivatePatchBinding
+import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.interfaces.ResourceHelper
+import javax.inject.Inject
+
+class MedtrumDeactivatePatchFragment : MedtrumBaseFragment() {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var rh: ResourceHelper
+
+ companion object {
+
+ fun newInstance(): MedtrumDeactivatePatchFragment = MedtrumDeactivatePatchFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_medtrum_deactivate_patch
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ aapsLogger.debug(LTag.PUMP, "MedtrumDeactivatePatchFragment onViewCreated")
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
+ viewModel?.apply {
+ setupStep.observe(viewLifecycleOwner) {
+ when (it) {
+ MedtrumViewModel.SetupStep.STOPPED -> {
+ moveStep(PatchStep.DEACTIVATION_COMPLETE)
+ }
+
+ MedtrumViewModel.SetupStep.ERROR -> {
+ moveStep(PatchStep.ERROR)
+ updateSetupStep(MedtrumViewModel.SetupStep.START_DEACTIVATION) // Reset setup step
+ binding.textDeactivatingPump.text = rh.gs(R.string.deactivating_error)
+ binding.btnNegative.visibility = View.VISIBLE
+ binding.btnPositive.visibility = View.VISIBLE
+ }
+
+ else -> Unit // Nothing to do here
+ }
+ }
+ deactivatePatch()
+ }
+ btnNegative.setOnClickListener {
+ OKDialog.showConfirmation(requireActivity(), rh.gs(R.string.cancel_sure)) {
+ viewModel?.apply {
+ moveStep(PatchStep.CANCEL)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumDeactivationCompleteFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumDeactivationCompleteFragment.kt
new file mode 100644
index 0000000000..b0d72d2395
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumDeactivationCompleteFragment.kt
@@ -0,0 +1,34 @@
+package info.nightscout.pump.medtrum.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.databinding.FragmentMedtrumDeactivationCompleteBinding
+import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import javax.inject.Inject
+
+class MedtrumDeactivationCompleteFragment : MedtrumBaseFragment() {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+
+ companion object {
+
+ fun newInstance(): MedtrumDeactivationCompleteFragment = MedtrumDeactivationCompleteFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_medtrum_deactivation_complete
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ aapsLogger.debug(LTag.PUMP, "MedtrumStartDeactivationFragment onViewCreated")
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
+ viewModel?.apply {
+ // Nothing to do here (yet)
+ }
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumOverviewFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumOverviewFragment.kt
new file mode 100644
index 0000000000..2a3907cbac
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumOverviewFragment.kt
@@ -0,0 +1,83 @@
+package info.nightscout.pump.medtrum.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.core.ui.toast.ToastUtils
+import info.nightscout.interfaces.protection.ProtectionCheck
+import info.nightscout.pump.medtrum.MedtrumPump
+import info.nightscout.pump.medtrum.databinding.FragmentMedtrumOverviewBinding
+import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumOverviewViewModel
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.code.EventType
+import info.nightscout.pump.medtrum.code.PatchStep
+import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
+import info.nightscout.rx.AapsSchedulers
+import info.nightscout.rx.logging.AAPSLogger
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import javax.inject.Inject
+
+class MedtrumOverviewFragment : MedtrumBaseFragment() {
+
+ @Inject lateinit var aapsSchedulers: AapsSchedulers
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var medtrumPump: MedtrumPump
+ @Inject lateinit var protectionCheck: ProtectionCheck
+
+ private var disposable: CompositeDisposable = CompositeDisposable()
+
+ override fun getLayoutId(): Int = R.layout.fragment_medtrum_overview
+
+ override fun onDestroy() {
+ super.onDestroy()
+ disposable.clear()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ binding.apply {
+ viewmodel = ViewModelProvider(this@MedtrumOverviewFragment, viewModelFactory).get(MedtrumOverviewViewModel::class.java)
+ viewmodel?.apply {
+ eventHandler.observe(viewLifecycleOwner) { evt ->
+ when (evt.peekContent()) {
+ EventType.CHANGE_PATCH_CLICKED -> requireContext().apply {
+ protectionCheck.queryProtection(
+ requireActivity(),
+ ProtectionCheck.Protection.PREFERENCES,
+ {
+ val nextStep = when {
+ medtrumPump.pumpState > MedtrumPumpState.EJECTED && medtrumPump.pumpState < MedtrumPumpState.STOPPED -> {
+ PatchStep.START_DEACTIVATION
+ }
+
+ medtrumPump.pumpState in listOf(MedtrumPumpState.STOPPED, MedtrumPumpState.NONE) -> {
+ PatchStep.PREPARE_PATCH
+ }
+
+ else -> {
+ PatchStep.RETRY_ACTIVATION
+ }
+ }
+ startActivity(MedtrumActivity.createIntentFromMenu(this, nextStep))
+ }
+ )
+ }
+
+ EventType.PROFILE_NOT_SET -> ToastUtils.infoToast(requireContext(), R.string.no_profile_selected)
+
+ else -> Unit
+ }
+ }
+ }
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPreparePatchConnectFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPreparePatchConnectFragment.kt
new file mode 100644
index 0000000000..57e05b2032
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPreparePatchConnectFragment.kt
@@ -0,0 +1,59 @@
+package info.nightscout.pump.medtrum.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.core.ui.dialogs.OKDialog
+import info.nightscout.core.ui.toast.ToastUtils
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.code.PatchStep
+import info.nightscout.pump.medtrum.databinding.FragmentMedtrumPreparePatchConnectBinding
+import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.interfaces.ResourceHelper
+import javax.inject.Inject
+
+class MedtrumPreparePatchConnectFragment : MedtrumBaseFragment() {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var rh: ResourceHelper
+
+ companion object {
+
+ fun newInstance(): MedtrumPreparePatchConnectFragment = MedtrumPreparePatchConnectFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_medtrum_prepare_patch_connect
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ aapsLogger.debug(LTag.PUMP, "MedtrumPreparePatchFragment onViewCreated")
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
+ viewModel?.apply {
+ setupStep.observe(viewLifecycleOwner) {
+ when (it) {
+ MedtrumViewModel.SetupStep.INITIAL -> btnPositive.visibility = View.GONE
+ MedtrumViewModel.SetupStep.FILLED -> btnPositive.visibility = View.VISIBLE
+
+ MedtrumViewModel.SetupStep.ERROR -> {
+ ToastUtils.errorToast(requireContext(), rh.gs(R.string.unexpected_state, it.toString()))
+ moveStep(PatchStep.CANCEL)
+ }
+
+ else -> Unit
+ }
+ }
+ preparePatchConnect()
+ }
+ btnNegative.setOnClickListener {
+ OKDialog.showConfirmation(requireActivity(), rh.gs(R.string.cancel_sure)) {
+ viewModel?.apply {
+ moveStep(PatchStep.CANCEL)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPreparePatchFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPreparePatchFragment.kt
new file mode 100644
index 0000000000..5e23d96632
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPreparePatchFragment.kt
@@ -0,0 +1,35 @@
+package info.nightscout.pump.medtrum.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.databinding.FragmentMedtrumPreparePatchBinding
+import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
+import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import javax.inject.Inject
+
+class MedtrumPreparePatchFragment : MedtrumBaseFragment() {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+
+ companion object {
+
+ fun newInstance(): MedtrumPreparePatchFragment = MedtrumPreparePatchFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_medtrum_prepare_patch
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ aapsLogger.debug(LTag.PUMP, "MedtrumPreparePatchFragment onViewCreated")
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
+ viewModel?.apply {
+ preparePatch()
+ }
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPrimeCompleteFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPrimeCompleteFragment.kt
new file mode 100644
index 0000000000..75b2bad284
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPrimeCompleteFragment.kt
@@ -0,0 +1,56 @@
+package info.nightscout.pump.medtrum.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.core.ui.dialogs.OKDialog
+import info.nightscout.core.ui.toast.ToastUtils
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.code.PatchStep
+import info.nightscout.pump.medtrum.databinding.FragmentMedtrumPrimeCompleteBinding
+import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
+import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.interfaces.ResourceHelper
+import javax.inject.Inject
+
+class MedtrumPrimeCompleteFragment : MedtrumBaseFragment() {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var rh: ResourceHelper
+
+ companion object {
+
+ fun newInstance(): MedtrumPrimeCompleteFragment = MedtrumPrimeCompleteFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_medtrum_prime_complete
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
+ viewModel?.apply {
+ setupStep.observe(viewLifecycleOwner) {
+ when (it) {
+ MedtrumViewModel.SetupStep.INITIAL,
+ MedtrumViewModel.SetupStep.PRIMED -> Unit // Nothing to do here, previous state
+
+ else -> {
+ ToastUtils.errorToast(requireContext(), rh.gs(R.string.unexpected_state, it.toString()))
+ aapsLogger.error(LTag.PUMP, "Unexpected state: $it")
+ }
+ }
+ }
+ }
+ btnNegative.setOnClickListener {
+ OKDialog.showConfirmation(requireActivity(), rh.gs(R.string.cancel_sure)) {
+ viewModel?.apply {
+ moveStep(PatchStep.CANCEL)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPrimeFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPrimeFragment.kt
new file mode 100644
index 0000000000..3322c8a54f
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPrimeFragment.kt
@@ -0,0 +1,56 @@
+package info.nightscout.pump.medtrum.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.core.ui.dialogs.OKDialog
+import info.nightscout.core.ui.toast.ToastUtils
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.code.PatchStep
+import info.nightscout.pump.medtrum.databinding.FragmentMedtrumPrimeBinding
+import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
+import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.interfaces.ResourceHelper
+import javax.inject.Inject
+
+class MedtrumPrimeFragment : MedtrumBaseFragment() {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var rh: ResourceHelper
+
+ companion object {
+
+ fun newInstance(): MedtrumPrimeFragment = MedtrumPrimeFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_medtrum_prime
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
+ viewModel?.apply {
+ setupStep.observe(viewLifecycleOwner) {
+ when (it) {
+ MedtrumViewModel.SetupStep.INITIAL,
+ MedtrumViewModel.SetupStep.FILLED -> Unit // Nothing to do here, previous state
+
+ else -> {
+ ToastUtils.errorToast(requireContext(), rh.gs(R.string.unexpected_state, it.toString()))
+ aapsLogger.error(LTag.PUMP, "Unexpected state: $it")
+ }
+ }
+ }
+ }
+ btnNegative.setOnClickListener {
+ OKDialog.showConfirmation(requireActivity(), rh.gs(R.string.cancel_sure)) {
+ viewModel?.apply {
+ moveStep(PatchStep.CANCEL)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPrimingFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPrimingFragment.kt
new file mode 100644
index 0000000000..fe1234a5c3
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPrimingFragment.kt
@@ -0,0 +1,67 @@
+package info.nightscout.pump.medtrum.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.core.ui.dialogs.OKDialog
+import info.nightscout.core.ui.toast.ToastUtils
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.code.PatchStep
+import info.nightscout.pump.medtrum.databinding.FragmentMedtrumPrimingBinding
+import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
+import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.interfaces.ResourceHelper
+import javax.inject.Inject
+
+class MedtrumPrimingFragment : MedtrumBaseFragment() {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var rh: ResourceHelper
+
+ companion object {
+
+ fun newInstance(): MedtrumPrimingFragment = MedtrumPrimingFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_medtrum_priming
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
+ viewModel?.apply {
+ setupStep.observe(viewLifecycleOwner) {
+ when (it) {
+ MedtrumViewModel.SetupStep.INITIAL,
+ MedtrumViewModel.SetupStep.FILLED,
+ MedtrumViewModel.SetupStep.PRIMING -> Unit // Nothing to do here
+ MedtrumViewModel.SetupStep.PRIMED -> moveStep(PatchStep.PRIME_COMPLETE)
+
+ MedtrumViewModel.SetupStep.ERROR -> {
+ moveStep(PatchStep.ERROR)
+ updateSetupStep(MedtrumViewModel.SetupStep.FILLED) // Reset setup step
+ binding.textWaitForPriming.text = rh.gs(R.string.priming_error)
+ binding.btnNegative.visibility = View.VISIBLE
+ binding.btnPositive.visibility = View.VISIBLE
+ }
+
+ else -> {
+ ToastUtils.errorToast(requireContext(), rh.gs(R.string.unexpected_state, it.toString()))
+ aapsLogger.error(LTag.PUMP, "Unexpected state: $it")
+ }
+ }
+ }
+ startPrime()
+ }
+ btnNegative.setOnClickListener {
+ OKDialog.showConfirmation(requireActivity(), rh.gs(R.string.cancel_sure)) {
+ viewModel?.apply {
+ moveStep(PatchStep.CANCEL)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumRetryActivationConnectFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumRetryActivationConnectFragment.kt
new file mode 100644
index 0000000000..f1e3c5614a
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumRetryActivationConnectFragment.kt
@@ -0,0 +1,60 @@
+package info.nightscout.pump.medtrum.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.core.ui.dialogs.OKDialog
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.code.PatchStep
+import info.nightscout.pump.medtrum.databinding.FragmentMedtrumRetryActivationConnectBinding
+import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
+import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.interfaces.ResourceHelper
+import javax.inject.Inject
+
+class MedtrumRetryActivationConnectFragment : MedtrumBaseFragment() {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var rh: ResourceHelper
+
+ companion object {
+
+ fun newInstance(): MedtrumRetryActivationConnectFragment = MedtrumRetryActivationConnectFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_medtrum_retry_activation_connect
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ aapsLogger.debug(LTag.PUMP, "MedtrumRetryActivationConnectFragment onViewCreated")
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
+ viewModel?.apply {
+
+ setupStep.observe(viewLifecycleOwner) {
+ when (it) {
+ MedtrumViewModel.SetupStep.INITIAL -> Unit // Nothing to do here
+ MedtrumViewModel.SetupStep.FILLED -> forceMoveStep(PatchStep.PRIME)
+ MedtrumViewModel.SetupStep.PRIMING -> forceMoveStep(PatchStep.PRIMING)
+ MedtrumViewModel.SetupStep.PRIMED -> forceMoveStep(PatchStep.PRIME_COMPLETE)
+ MedtrumViewModel.SetupStep.ACTIVATED -> forceMoveStep(PatchStep.ACTIVATE_COMPLETE)
+
+ else -> {
+ aapsLogger.error(LTag.PUMP, "Unexpected state: $it")
+ }
+ }
+ }
+ retryActivationConnect()
+ }
+ btnNegative.setOnClickListener {
+ OKDialog.showConfirmation(requireActivity(), rh.gs(R.string.cancel_sure)) {
+ viewModel?.apply {
+ moveStep(PatchStep.CANCEL)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumRetryActivationFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumRetryActivationFragment.kt
new file mode 100644
index 0000000000..5e1673685b
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumRetryActivationFragment.kt
@@ -0,0 +1,46 @@
+package info.nightscout.pump.medtrum.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.core.ui.dialogs.OKDialog
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.code.PatchStep
+import info.nightscout.pump.medtrum.databinding.FragmentMedtrumRetryActivationBinding
+import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
+import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.interfaces.ResourceHelper
+import javax.inject.Inject
+
+class MedtrumRetryActivationFragment : MedtrumBaseFragment() {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var rh: ResourceHelper
+
+ companion object {
+
+ fun newInstance(): MedtrumRetryActivationFragment = MedtrumRetryActivationFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_medtrum_retry_activation
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ aapsLogger.debug(LTag.PUMP, "MedtrumRetryActivationFragment onViewCreated")
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
+ viewModel?.apply {
+ preparePatch() // Use this to make sure we are disconnceted at this stage
+ }
+ btnNegative.setOnClickListener {
+ OKDialog.showConfirmation(requireActivity(), rh.gs(R.string.medtrum_deactivate_pump_confirm)) {
+ viewModel?.apply {
+ moveStep(PatchStep.FORCE_DEACTIVATION)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumStartDeactivationFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumStartDeactivationFragment.kt
new file mode 100644
index 0000000000..94f3bd93d9
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumStartDeactivationFragment.kt
@@ -0,0 +1,46 @@
+package info.nightscout.pump.medtrum.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.core.ui.dialogs.OKDialog
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.code.PatchStep
+import info.nightscout.pump.medtrum.databinding.FragmentMedtrumStartDeactivationBinding
+import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
+import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.interfaces.ResourceHelper
+import javax.inject.Inject
+
+class MedtrumStartDeactivationFragment : MedtrumBaseFragment() {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var rh: ResourceHelper
+
+ companion object {
+
+ fun newInstance(): MedtrumStartDeactivationFragment = MedtrumStartDeactivationFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_medtrum_start_deactivation
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ aapsLogger.debug(LTag.PUMP, "MedtrumStartDeactivationFragment onViewCreated")
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
+ viewModel?.apply {
+ updateSetupStep(MedtrumViewModel.SetupStep.START_DEACTIVATION)
+ }
+ btnPositive.setOnClickListener {
+ OKDialog.showConfirmation(requireActivity(), rh.gs(R.string.medtrum_deactivate_pump_confirm)) {
+ viewModel?.apply {
+ moveStep(PatchStep.DEACTIVATE)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/event/SingleLiveEvent.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/event/SingleLiveEvent.kt
new file mode 100644
index 0000000000..4d09335375
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/event/SingleLiveEvent.kt
@@ -0,0 +1,29 @@
+package info.nightscout.pump.medtrum.ui.event
+
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+import androidx.annotation.MainThread
+import java.util.concurrent.atomic.AtomicBoolean
+
+open class SingleLiveEvent : MutableLiveData() {
+ private val mPending = AtomicBoolean(false)
+ override fun observe(owner: LifecycleOwner, observer: Observer) {
+ super.observe(owner) { t ->
+ if (mPending.compareAndSet(true, false)) {
+ observer.onChanged(t)
+ }
+ }
+ }
+
+ @MainThread
+ override fun setValue(t: T?) {
+ mPending.set(true)
+ super.setValue(t)
+ }
+
+ @MainThread
+ fun call() {
+ value = null
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/event/UIEvent.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/event/UIEvent.kt
new file mode 100644
index 0000000000..e1f879799b
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/event/UIEvent.kt
@@ -0,0 +1,7 @@
+package info.nightscout.pump.medtrum.ui.event
+
+open class UIEvent(private val content: T) {
+ var value: Any? = null
+ fun peekContent(): T = content
+}
+
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/BaseViewModel.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/BaseViewModel.kt
new file mode 100644
index 0000000000..9b2a3bbbde
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/BaseViewModel.kt
@@ -0,0 +1,30 @@
+package info.nightscout.pump.medtrum.ui.viewmodel
+
+import androidx.lifecycle.ViewModel
+import info.nightscout.pump.medtrum.ui.MedtrumBaseNavigator
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.Disposable
+import java.lang.ref.WeakReference
+
+abstract class BaseViewModel : ViewModel() {
+
+ private var _navigator: WeakReference? = null
+ var navigator: N?
+ set(value) {
+ _navigator = WeakReference(value)
+ }
+ get() = _navigator?.get()
+
+ private val compositeDisposable = CompositeDisposable()
+
+ override fun onCleared() {
+ compositeDisposable.clear()
+ super.onCleared()
+ }
+
+ fun back() = navigator?.back()
+
+ fun finish() = navigator?.finish()
+
+ fun Disposable.addTo() = apply { compositeDisposable.add(this) }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/MedtrumOverviewViewModel.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/MedtrumOverviewViewModel.kt
new file mode 100644
index 0000000000..da742ea91d
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/MedtrumOverviewViewModel.kt
@@ -0,0 +1,189 @@
+package info.nightscout.pump.medtrum.ui.viewmodel
+
+import androidx.lifecycle.LiveData
+import info.nightscout.pump.medtrum.code.EventType
+import info.nightscout.pump.medtrum.ui.MedtrumBaseNavigator
+import info.nightscout.pump.medtrum.ui.event.SingleLiveEvent
+import info.nightscout.pump.medtrum.ui.event.UIEvent
+import info.nightscout.interfaces.profile.ProfileFunction
+import info.nightscout.interfaces.queue.CommandQueue
+import info.nightscout.pump.medtrum.MedtrumPump
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.code.ConnectionState
+import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import info.nightscout.shared.interfaces.ResourceHelper
+import info.nightscout.shared.utils.DateUtil
+import info.nightscout.shared.utils.T
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+class MedtrumOverviewViewModel @Inject constructor(
+ private val aapsLogger: AAPSLogger,
+ private val rh: ResourceHelper,
+ private val profileFunction: ProfileFunction,
+ private val commandQueue: CommandQueue,
+ private val dateUtil: DateUtil,
+ val medtrumPump: MedtrumPump
+) : BaseViewModel() {
+
+ private val scope = CoroutineScope(Dispatchers.Default)
+
+ private val _eventHandler = SingleLiveEvent>()
+ val eventHandler: LiveData>
+ get() = _eventHandler
+
+ private val _canDoRefresh = SingleLiveEvent()
+ val canDoRefresh: LiveData
+ get() = _canDoRefresh
+
+ private val _canDoResetAlarms = SingleLiveEvent()
+ val canDoResetAlarms: LiveData
+ get() = _canDoResetAlarms
+
+ private val _bleStatus = SingleLiveEvent()
+ val bleStatus: LiveData
+ get() = _bleStatus
+
+ private val _lastConnectionMinAgo = SingleLiveEvent()
+ val lastConnectionMinAgo: LiveData
+ get() = _lastConnectionMinAgo
+
+ private val _lastBolus = SingleLiveEvent()
+ val lastBolus: LiveData
+ get() = _lastBolus
+
+ private val _activeAlarms = SingleLiveEvent()
+ val activeAlarms: LiveData
+ get() = _activeAlarms
+
+ private val _pumpType = SingleLiveEvent()
+ val pumpType: LiveData
+ get() = _pumpType
+
+ private val _fwVersion = SingleLiveEvent()
+ val fwVersion: LiveData
+ get() = _fwVersion
+
+ private val _patchNo = SingleLiveEvent()
+ val patchNo: LiveData
+ get() = _patchNo
+
+ private val _patchExpiry = SingleLiveEvent()
+ val patchExpiry: LiveData
+ get() = _patchExpiry
+
+ init {
+ scope.launch {
+ medtrumPump.connectionStateFlow.collect { state ->
+ aapsLogger.debug(LTag.PUMP, "MedtrumViewModel connectionStateFlow: $state")
+ when (state) {
+ ConnectionState.CONNECTING -> {
+ _bleStatus.postValue("{fa-bluetooth-b spin}")
+ _canDoRefresh.postValue(false)
+ }
+
+ ConnectionState.CONNECTED -> {
+ _bleStatus.postValue("{fa-bluetooth}")
+ _canDoRefresh.postValue(false)
+ }
+
+ ConnectionState.DISCONNECTED -> {
+ _bleStatus.postValue("{fa-bluetooth-b}")
+ if (medtrumPump.pumpState > MedtrumPumpState.EJECTED && medtrumPump.pumpState < MedtrumPumpState.STOPPED) {
+ _canDoRefresh.postValue(true)
+ } else {
+ _canDoRefresh.postValue(false)
+ }
+ }
+
+ ConnectionState.DISCONNECTING -> {
+ _bleStatus.postValue("{fa-bluetooth-b spin}")
+ _canDoRefresh.postValue(false)
+ }
+ }
+ updateGUI()
+ }
+ }
+ scope.launch {
+ medtrumPump.pumpStateFlow.collect { state ->
+ aapsLogger.debug(LTag.PUMP, "MedtrumViewModel pumpStateFlow: $state")
+ _canDoResetAlarms.postValue(
+ medtrumPump.pumpState in listOf(
+ MedtrumPumpState.PAUSED, MedtrumPumpState.HMAX_SUSPENDED, MedtrumPumpState.DMAX_SUSPENDED
+ )
+ )
+
+ updateGUI()
+ }
+ }
+ // Periodically update gui
+ scope.launch {
+ while (true) {
+ updateGUI()
+ kotlinx.coroutines.delay(T.mins(1).msecs())
+ }
+ }
+ }
+
+ override fun onCleared() {
+ super.onCleared()
+ scope.cancel()
+ }
+
+ fun onClickRefresh() {
+ commandQueue.readStatus(rh.gs(R.string.requested_by_user), null)
+ }
+
+ fun onClickResetAlarms() {
+ commandQueue.clearAlarms(null)
+ }
+
+ fun onClickChangePatch() {
+ aapsLogger.debug(LTag.PUMP, "ChangePatch Patch clicked!")
+ val profile = profileFunction.getProfile()
+ if (profile == null) {
+ _eventHandler.postValue(UIEvent(EventType.PROFILE_NOT_SET))
+ } else {
+ _eventHandler.postValue(UIEvent(EventType.CHANGE_PATCH_CLICKED))
+ }
+ }
+
+ fun updateGUI() {
+ // Update less dynamic values
+ if (medtrumPump.lastConnection != 0L) {
+ val agoMilliseconds = System.currentTimeMillis() - medtrumPump.lastConnection
+ val agoMinutes = agoMilliseconds / 1000 / 60
+ _lastConnectionMinAgo.postValue(rh.gs(info.nightscout.shared.R.string.minago, agoMinutes))
+ }
+ if (medtrumPump.lastBolusTime != 0L) {
+ val agoMilliseconds = System.currentTimeMillis() - medtrumPump.lastBolusTime
+ val agoHours = agoMilliseconds.toDouble() / 60.0 / 60.0 / 1000.0
+ if (agoHours < 6)
+ // max 6h back
+ _lastBolus.postValue(
+ dateUtil.timeString(medtrumPump.lastBolusTime) + " " + dateUtil.sinceString(medtrumPump.lastBolusTime, rh) + " " + rh.gs(
+ info.nightscout.interfaces.R.string.format_insulin_units, medtrumPump.lastBolusAmount
+ )
+ )
+ else _lastBolus.postValue("")
+ }
+
+ val activeAlarmStrings = medtrumPump.activeAlarms.map { medtrumPump.alarmStateToString(it) }
+ _activeAlarms.postValue(activeAlarmStrings.joinToString("\n"))
+ _pumpType.postValue(medtrumPump.deviceType.toString())
+ _fwVersion.postValue(medtrumPump.swVersion)
+ _patchNo.postValue(medtrumPump.patchId.toString())
+
+ if (medtrumPump.desiredPatchExpiration) {
+ val expiry = medtrumPump.patchStartTime + T.hours(72).msecs()
+ _patchExpiry.postValue(dateUtil.dateAndTimeString(expiry))
+ } else {
+ _patchExpiry.postValue(rh.gs(R.string.expiry_not_enabled))
+ }
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/MedtrumViewModel.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/MedtrumViewModel.kt
new file mode 100644
index 0000000000..da2245ce21
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/MedtrumViewModel.kt
@@ -0,0 +1,327 @@
+package info.nightscout.pump.medtrum.ui.viewmodel
+
+import android.os.SystemClock
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import info.nightscout.interfaces.queue.Callback
+import info.nightscout.interfaces.queue.CommandQueue
+import info.nightscout.pump.medtrum.MedtrumPlugin
+import info.nightscout.pump.medtrum.MedtrumPump
+import info.nightscout.pump.medtrum.R
+import info.nightscout.pump.medtrum.code.ConnectionState
+import info.nightscout.pump.medtrum.services.MedtrumService
+import info.nightscout.pump.medtrum.code.EventType
+import info.nightscout.pump.medtrum.code.PatchStep
+import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
+import info.nightscout.pump.medtrum.encryption.Crypt
+import info.nightscout.pump.medtrum.ui.MedtrumBaseNavigator
+import info.nightscout.pump.medtrum.ui.event.SingleLiveEvent
+import info.nightscout.pump.medtrum.ui.event.UIEvent
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.logging.LTag
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+class MedtrumViewModel @Inject constructor(
+ private val aapsLogger: AAPSLogger,
+ private val medtrumPlugin: MedtrumPlugin,
+ private val commandQueue: CommandQueue,
+ val medtrumPump: MedtrumPump
+) : BaseViewModel() {
+
+ val patchStep = MutableLiveData()
+
+ val medtrumService: MedtrumService?
+ get() = medtrumPlugin.getService()
+
+ private val scope = CoroutineScope(Dispatchers.Default)
+
+ private val _title = MutableLiveData(R.string.step_prepare_patch)
+ val title: LiveData
+ get() = _title
+
+ private val _eventHandler = SingleLiveEvent>()
+ val eventHandler: LiveData>
+ get() = _eventHandler
+
+ private var mInitPatchStep: PatchStep? = null
+ private var connectRetryCounter = 0
+
+ init {
+ scope.launch {
+ medtrumPump.connectionStateFlow.collect { state ->
+ aapsLogger.debug(LTag.PUMP, "MedtrumViewModel connectionStateFlow: $state")
+ if (patchStep.value != null) {
+ when (state) {
+ ConnectionState.CONNECTED -> {
+ medtrumPump.lastConnection = System.currentTimeMillis()
+ }
+
+ ConnectionState.DISCONNECTED -> {
+ if (patchStep.value in listOf(
+ PatchStep.PRIME,
+ PatchStep.PRIMING,
+ PatchStep.PRIME_COMPLETE,
+ PatchStep.ATTACH_PATCH,
+ PatchStep.ACTIVATE
+ )
+ ) {
+ medtrumService?.connect("Try reconnect from viewModel")
+ }
+ if (patchStep.value in listOf(PatchStep.PREPARE_PATCH_CONNECT, PatchStep.RETRY_ACTIVATION_CONNECT)) {
+ // We are disconnected during prepare patch connect, this means we failed to connect (wrong session token?)
+ // Retry 3 times, then give up
+ if (connectRetryCounter < 3) {
+ connectRetryCounter++
+ aapsLogger.info(LTag.PUMP, "preparePatchConnect: retry $connectRetryCounter")
+ medtrumService?.connect("Try reconnect from viewModel")
+ } else {
+ aapsLogger.info(LTag.PUMP, "preparePatchConnect: failed to connect")
+ updateSetupStep(SetupStep.ERROR)
+ }
+ }
+ }
+
+ ConnectionState.CONNECTING, ConnectionState.DISCONNECTING -> {
+ }
+ }
+ }
+ }
+ }
+ scope.launch {
+ medtrumPump.pumpStateFlow.collect { state ->
+ aapsLogger.debug(LTag.PUMP, "MedtrumViewModel pumpStateFlow: $state")
+ if (patchStep.value != null) {
+ when (state) {
+ MedtrumPumpState.NONE, MedtrumPumpState.IDLE -> {
+ updateSetupStep(SetupStep.INITIAL)
+ }
+
+ MedtrumPumpState.FILLED -> {
+ updateSetupStep(SetupStep.FILLED)
+ }
+
+ MedtrumPumpState.PRIMING -> {
+ updateSetupStep(SetupStep.PRIMING)
+ }
+
+ MedtrumPumpState.PRIMED, MedtrumPumpState.EJECTED -> {
+ updateSetupStep(SetupStep.PRIMED)
+ }
+
+ MedtrumPumpState.ACTIVE, MedtrumPumpState.ACTIVE_ALT -> {
+ updateSetupStep(SetupStep.ACTIVATED)
+ }
+
+ MedtrumPumpState.STOPPED -> {
+ updateSetupStep(SetupStep.STOPPED)
+ }
+
+ else -> {
+ updateSetupStep(SetupStep.ERROR)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ override fun onCleared() {
+ super.onCleared()
+ scope.cancel()
+ }
+
+ fun moveStep(newPatchStep: PatchStep) {
+ val oldPatchStep = patchStep.value
+
+ if (oldPatchStep != newPatchStep) {
+ when (newPatchStep) {
+ PatchStep.CANCEL -> {
+ if (oldPatchStep !in listOf(
+ PatchStep.PREPARE_PATCH,
+ PatchStep.START_DEACTIVATION,
+ PatchStep.DEACTIVATE,
+ PatchStep.FORCE_DEACTIVATION,
+ PatchStep.DEACTIVATION_COMPLETE
+ )
+ ) {
+ medtrumService?.disconnect("Cancel")
+ }
+ }
+
+ PatchStep.COMPLETE -> {
+ medtrumService?.disconnect("Complete")
+ }
+
+ PatchStep.START_DEACTIVATION,
+ PatchStep.DEACTIVATE,
+ PatchStep.FORCE_DEACTIVATION,
+ PatchStep.DEACTIVATION_COMPLETE,
+ PatchStep.PREPARE_PATCH,
+ PatchStep.RETRY_ACTIVATION,
+ PatchStep.RETRY_ACTIVATION_CONNECT -> {
+ // Do nothing, deactivation uses commandQueue to control connection
+ }
+
+ PatchStep.PREPARE_PATCH_CONNECT -> {
+ // Make sure we are disconnected, else dont move step
+ if (medtrumService?.isConnected == true) {
+ aapsLogger.info(LTag.PUMP, "moveStep: connected, not moving step")
+ return
+ } else {
+ }
+ }
+
+ else -> {
+ // Make sure we are connected, else dont move step
+ if (medtrumService?.isConnected == false) {
+ aapsLogger.info(LTag.PUMP, "moveStep: not connected, not moving step")
+ return
+ } else {
+ }
+ }
+ }
+ }
+
+ prepareStep(newPatchStep)
+
+ aapsLogger.info(LTag.PUMP, "moveStep: $oldPatchStep -> $newPatchStep")
+ }
+
+ fun forceMoveStep(newPatchStep: PatchStep) {
+ val oldPatchStep = patchStep.value
+
+ prepareStep(newPatchStep)
+ aapsLogger.info(LTag.PUMP, "forceMoveStep: $oldPatchStep -> $newPatchStep")
+ }
+
+ fun initializePatchStep(step: PatchStep) {
+ mInitPatchStep = prepareStep(step)
+ }
+
+ fun preparePatch() {
+ medtrumService?.disconnect("PreparePatch")
+ }
+
+ fun preparePatchConnect() {
+ scope.launch {
+ if (medtrumService?.isConnected == false) {
+ aapsLogger.info(LTag.PUMP, "preparePatch: new session")
+ // New session, generate new session token
+ medtrumPump.patchSessionToken = Crypt().generateRandomToken()
+ // Connect to pump
+ medtrumService?.connect("PreparePatch")
+ } else {
+ aapsLogger.error(LTag.PUMP, "preparePatch: Already connected when trying to prepare patch")
+ // Do nothing, we are already connected
+ }
+ }
+ }
+
+ fun startPrime() {
+ scope.launch {
+ if (medtrumPump.pumpState == MedtrumPumpState.PRIMING) {
+ aapsLogger.info(LTag.PUMP, "startPrime: already priming!")
+ } else {
+ if (medtrumService?.startPrime() == true) {
+ aapsLogger.info(LTag.PUMP, "startPrime: success!")
+ } else {
+ aapsLogger.info(LTag.PUMP, "startPrime: failure!")
+ updateSetupStep(SetupStep.ERROR)
+ }
+ }
+ }
+ }
+
+ fun startActivate() {
+ scope.launch {
+ if (medtrumService?.startActivate() == true) {
+ aapsLogger.info(LTag.PUMP, "startActivate: success!")
+ } else {
+ aapsLogger.info(LTag.PUMP, "startActivate: failure!")
+ updateSetupStep(SetupStep.ERROR)
+ }
+ }
+ }
+
+ fun deactivatePatch() {
+ commandQueue.deactivate(object : Callback() {
+ override fun run() {
+ if (this.result.success) {
+ // Do nothing, state change will handle this
+ } else {
+ if (medtrumPump.pumpState >= MedtrumPumpState.OCCLUSION && medtrumPump.pumpState <= MedtrumPumpState.NO_CALIBRATION) {
+ // We are in a fault state, we need to force deactivation
+ aapsLogger.info(LTag.PUMP, "deactivatePatch: force deactivation")
+ medtrumService?.disconnect("ForceDeactivation")
+ SystemClock.sleep(1000)
+ medtrumPump.pumpState = MedtrumPumpState.STOPPED
+ } else {
+ aapsLogger.info(LTag.PUMP, "deactivatePatch: failure!")
+ updateSetupStep(SetupStep.ERROR)
+ }
+ }
+ }
+ })
+ }
+
+ fun retryActivationConnect() {
+ scope.launch {
+ if (medtrumService?.isConnected == false) {
+ // Reset medtrum pump state, we will pickup pomp state on connect
+ medtrumPump.pumpState = MedtrumPumpState.NONE
+ medtrumService?.connect("RetryActivationConnect")
+ } else {
+ aapsLogger.error(LTag.PUMP, "retryActivationConnect: Already connected when trying to prepare patch")
+ updateSetupStep(SetupStep.ERROR)
+ }
+ }
+ }
+
+ private fun prepareStep(newStep: PatchStep): PatchStep {
+ val stringResId = when (newStep) {
+ PatchStep.PREPARE_PATCH -> R.string.step_prepare_patch
+ PatchStep.PREPARE_PATCH_CONNECT -> R.string.step_prepare_patch_connect
+ PatchStep.PRIME -> R.string.step_prime
+ PatchStep.PRIMING -> R.string.step_priming
+ PatchStep.PRIME_COMPLETE -> R.string.step_priming_complete
+ PatchStep.ATTACH_PATCH -> R.string.step_attach
+ PatchStep.ACTIVATE -> R.string.step_activate
+ PatchStep.ACTIVATE_COMPLETE -> R.string.step_activate_complete
+ PatchStep.START_DEACTIVATION -> R.string.step_deactivate
+ PatchStep.DEACTIVATE -> R.string.step_deactivating
+ PatchStep.DEACTIVATION_COMPLETE -> R.string.step_deactivate_complete
+ PatchStep.RETRY_ACTIVATION,
+ PatchStep.RETRY_ACTIVATION_CONNECT -> R.string.step_retry_activation
+
+ PatchStep.COMPLETE,
+ PatchStep.FORCE_DEACTIVATION,
+ PatchStep.ERROR,
+ PatchStep.CANCEL -> _title.value
+ }
+
+ val currentTitle = _title.value
+ aapsLogger.info(LTag.PUMP, "prepareStep: title before cond: $stringResId")
+ if (currentTitle != stringResId) {
+ aapsLogger.info(LTag.PUMP, "prepareStep: title: $stringResId")
+ _title.postValue(stringResId)
+ }
+
+ patchStep.postValue(newStep)
+
+ return newStep
+ }
+
+ enum class SetupStep { INITIAL, FILLED, PRIMING, PRIMED, ACTIVATED, ERROR, START_DEACTIVATION, STOPPED
+ }
+
+ val setupStep = MutableLiveData()
+
+ fun updateSetupStep(newSetupStep: SetupStep) {
+ aapsLogger.info(LTag.PUMP, "curSetupStep: ${setupStep.value}, newSetupStep: $newSetupStep")
+ setupStep.postValue(newSetupStep)
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/ViewModelFactory.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/ViewModelFactory.kt
new file mode 100644
index 0000000000..0baf6a7679
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/ViewModelFactory.kt
@@ -0,0 +1,40 @@
+package info.nightscout.pump.medtrum.ui.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import dagger.MapKey
+import javax.inject.Inject
+import javax.inject.Provider
+import javax.inject.Singleton
+import kotlin.reflect.KClass
+
+@MustBeDocumented
+@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
+@Retention(AnnotationRetention.RUNTIME)
+@MapKey
+internal annotation class ViewModelKey(val value: KClass)
+
+@Singleton
+class ViewModelFactory @Inject constructor(private val creators: Map, @JvmSuppressWildcards Provider>) : ViewModelProvider.Factory {
+ override fun create(modelClass: Class): T {
+ var creator: Provider? = creators[modelClass]
+ if (creator == null) {
+ for ((key, value) in creators) {
+ if (modelClass.isAssignableFrom(key)) {
+ creator = value
+ break
+ }
+ }
+ }
+ if (creator == null) {
+ throw IllegalArgumentException("unknown model class $modelClass")
+ }
+ try {
+ @Suppress("UNCHECKED_CAST")
+ return creator.get() as T
+ } catch (e: Exception) {
+ throw IllegalStateException(e)
+ }
+
+ }
+}
diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/util/MedtrumTimeUtil.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/util/MedtrumTimeUtil.kt
new file mode 100644
index 0000000000..ad3910f0ec
--- /dev/null
+++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/util/MedtrumTimeUtil.kt
@@ -0,0 +1,26 @@
+package info.nightscout.pump.medtrum.util
+
+import java.time.Duration
+import java.time.Instant
+
+class MedtrumTimeUtil {
+
+ fun getCurrentTimePumpSeconds() : Long {
+ val startInstant = Instant.parse("2014-01-01T00:00:00Z")
+ val currentInstant = Instant.now()
+ return Duration.between(startInstant, currentInstant).seconds
+ }
+
+ fun getCurrentTimePumpMillis() : Long {
+ val startInstant = Instant.parse("2014-01-01T00:00:00Z")
+ val currentInstant = Instant.now()
+ return Duration.between(startInstant, currentInstant).seconds * 1000
+ }
+
+ fun convertPumpTimeToSystemTimeMillis(pumpTime: Long) : Long {
+ val startInstant = Instant.parse("2014-01-01T00:00:00Z")
+ val pumpInstant = startInstant.plusSeconds(pumpTime)
+ val epochInstant = Instant.EPOCH
+ return Duration.between(epochInstant, pumpInstant).seconds * 1000
+ }
+}
diff --git a/pump/medtrum/src/main/res/drawable/ic_refresh_conn.xml b/pump/medtrum/src/main/res/drawable/ic_refresh_conn.xml
new file mode 100644
index 0000000000..851d544e89
--- /dev/null
+++ b/pump/medtrum/src/main/res/drawable/ic_refresh_conn.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/pump/medtrum/src/main/res/drawable/ic_silence_alerts.xml b/pump/medtrum/src/main/res/drawable/ic_silence_alerts.xml
new file mode 100644
index 0000000000..0688820a25
--- /dev/null
+++ b/pump/medtrum/src/main/res/drawable/ic_silence_alerts.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/pump/medtrum/src/main/res/layout/activity_medtrum.xml b/pump/medtrum/src/main/res/layout/activity_medtrum.xml
new file mode 100644
index 0000000000..2c1d16aad2
--- /dev/null
+++ b/pump/medtrum/src/main/res/layout/activity_medtrum.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pump/medtrum/src/main/res/layout/fragment_medtrum_activate.xml b/pump/medtrum/src/main/res/layout/fragment_medtrum_activate.xml
new file mode 100644
index 0000000000..517e8bd56b
--- /dev/null
+++ b/pump/medtrum/src/main/res/layout/fragment_medtrum_activate.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pump/medtrum/src/main/res/layout/fragment_medtrum_activate_complete.xml b/pump/medtrum/src/main/res/layout/fragment_medtrum_activate_complete.xml
new file mode 100644
index 0000000000..aa93e8f5e9
--- /dev/null
+++ b/pump/medtrum/src/main/res/layout/fragment_medtrum_activate_complete.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pump/medtrum/src/main/res/layout/fragment_medtrum_attach_patch.xml b/pump/medtrum/src/main/res/layout/fragment_medtrum_attach_patch.xml
new file mode 100644
index 0000000000..b94fb6b4ff
--- /dev/null
+++ b/pump/medtrum/src/main/res/layout/fragment_medtrum_attach_patch.xml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pump/medtrum/src/main/res/layout/fragment_medtrum_deactivate_patch.xml b/pump/medtrum/src/main/res/layout/fragment_medtrum_deactivate_patch.xml
new file mode 100644
index 0000000000..7d58f38e02
--- /dev/null
+++ b/pump/medtrum/src/main/res/layout/fragment_medtrum_deactivate_patch.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pump/medtrum/src/main/res/layout/fragment_medtrum_deactivation_complete.xml b/pump/medtrum/src/main/res/layout/fragment_medtrum_deactivation_complete.xml
new file mode 100644
index 0000000000..cc564f230f
--- /dev/null
+++ b/pump/medtrum/src/main/res/layout/fragment_medtrum_deactivation_complete.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pump/medtrum/src/main/res/layout/fragment_medtrum_overview.xml b/pump/medtrum/src/main/res/layout/fragment_medtrum_overview.xml
new file mode 100644
index 0000000000..897fa06ef6
--- /dev/null
+++ b/pump/medtrum/src/main/res/layout/fragment_medtrum_overview.xml
@@ -0,0 +1,679 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pump/medtrum/src/main/res/layout/fragment_medtrum_prepare_patch.xml b/pump/medtrum/src/main/res/layout/fragment_medtrum_prepare_patch.xml
new file mode 100644
index 0000000000..b9b654575f
--- /dev/null
+++ b/pump/medtrum/src/main/res/layout/fragment_medtrum_prepare_patch.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pump/medtrum/src/main/res/layout/fragment_medtrum_prepare_patch_connect.xml b/pump/medtrum/src/main/res/layout/fragment_medtrum_prepare_patch_connect.xml
new file mode 100644
index 0000000000..06843e91c4
--- /dev/null
+++ b/pump/medtrum/src/main/res/layout/fragment_medtrum_prepare_patch_connect.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pump/medtrum/src/main/res/layout/fragment_medtrum_prime.xml b/pump/medtrum/src/main/res/layout/fragment_medtrum_prime.xml
new file mode 100644
index 0000000000..e5ba59c7c2
--- /dev/null
+++ b/pump/medtrum/src/main/res/layout/fragment_medtrum_prime.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pump/medtrum/src/main/res/layout/fragment_medtrum_prime_complete.xml b/pump/medtrum/src/main/res/layout/fragment_medtrum_prime_complete.xml
new file mode 100644
index 0000000000..071e755529
--- /dev/null
+++ b/pump/medtrum/src/main/res/layout/fragment_medtrum_prime_complete.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pump/medtrum/src/main/res/layout/fragment_medtrum_priming.xml b/pump/medtrum/src/main/res/layout/fragment_medtrum_priming.xml
new file mode 100644
index 0000000000..cebfa521df
--- /dev/null
+++ b/pump/medtrum/src/main/res/layout/fragment_medtrum_priming.xml
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pump/medtrum/src/main/res/layout/fragment_medtrum_retry_activation.xml b/pump/medtrum/src/main/res/layout/fragment_medtrum_retry_activation.xml
new file mode 100644
index 0000000000..7911876aa1
--- /dev/null
+++ b/pump/medtrum/src/main/res/layout/fragment_medtrum_retry_activation.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pump/medtrum/src/main/res/layout/fragment_medtrum_retry_activation_connect.xml b/pump/medtrum/src/main/res/layout/fragment_medtrum_retry_activation_connect.xml
new file mode 100644
index 0000000000..852b0e6e73
--- /dev/null
+++ b/pump/medtrum/src/main/res/layout/fragment_medtrum_retry_activation_connect.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pump/medtrum/src/main/res/layout/fragment_medtrum_start_deactivation.xml b/pump/medtrum/src/main/res/layout/fragment_medtrum_start_deactivation.xml
new file mode 100644
index 0000000000..bf58641e9f
--- /dev/null
+++ b/pump/medtrum/src/main/res/layout/fragment_medtrum_start_deactivation.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pump/medtrum/src/main/res/values/arrays.xml b/pump/medtrum/src/main/res/values/arrays.xml
new file mode 100644
index 0000000000..f2184b0875
--- /dev/null
+++ b/pump/medtrum/src/main/res/values/arrays.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+ - Vibrate and beep
+ - Vibrate only
+ - Beep only
+ - None
+
+
+
+
+
+
+ - 4
+ - 5
+ - 6
+ - 7
+
+
diff --git a/pump/medtrum/src/main/res/values/strings.xml b/pump/medtrum/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..575782c9eb
--- /dev/null
+++ b/pump/medtrum/src/main/res/values/strings.xml
@@ -0,0 +1,151 @@
+
+
+
+ sn_input
+ alarm_setting
+ patch_expiration
+ hourly_max_insulin
+ daily_max_insulin
+
+ medtrumpump_settings
+ pump_state
+ active_alarms
+ last_connection
+ last_bolus_time
+ last_bolus_amount
+ medtrum_session_token
+ patch_id
+ device_type
+ sw_version
+ patch_start_time
+ actual_basal_profile
+ current_sequence_number
+ synced_sequence_number
+ pump_time_zone_offset
+
+ Medtrum
+ MT
+ Pump integration for Medtrum Nano
+ Medtrum pump settings
+ Pump error: %1$s !!
+ Pump untested: %1$d! Please contact us at discord or github for support
+ Pump is suspended
+ Pump is suspended due to hourly max insulin exceeded
+ Pump is suspended due to daily max insulin exceeded
+ Patch not activated
+ Setting user settings to pump failed!
+
+
+ BLE Status
+ Last connected
+ Pump state
+ Active alarms
+ %.2f U
+ %.2f V
+ Basal type
+ Basal rate
+ %.2f U/h
+ Pump type
+ FW version
+ Patch no
+ Patch expires
+ Refresh
+ Reset alarms
+ Change patch
+ Requested by user
+ Not enabled
+
+
+ None
+ Pump low battery
+ Pump low reservoir
+ Pump expires soon
+ Low BG suspended
+ Low BG suspended 2
+ Auto suspended
+ hourly max suspended
+ daily max suspended
+ Suspended
+ Paused
+ Occlusion
+ Expired
+ Reservoir empty
+ Patch fault
+ Patch fault 2
+ Base fault
+ Battery out
+ No calibration
+ Failed to update pump timezone, snooze message and refresh manually.
+
+
+ Retry
+ Next
+ Discard
+
+ Activate Patch
+ Connect and Fill
+ Prime
+ Priming
+ Priming Complete
+ Attach Patch
+ Activating...
+ Activation Complete
+ Deactivate Patch
+ Deactivating...
+ Patch deactivated
+ Activation in progress
+ Unexpected state: %1$s
+ No profile selected. Please select a profile and try again.
+
+ Pump Base Serial: %1$X
+ No active patch. Press Next to begin the activation process.
+ Pump base should not be connected to the patch until the next step!
+ Connect pump base to a new patch, remove the residual air and fill with insulin, then press Next.
+ Note: A minimum of 70 units is required for actvation.
+ Do not attach the patch to the body yet.
+ Half-press needle button. Then tap Next to start prime.
+ Please wait for the priming to complete.
+ Failed to prime, press Retry to try again.
+ Press Next to continue.
+ Press Next to start activation.
+ Remove the safety lock. Attach the pump to the body. Press the needle button.
+ Remove the safety lock. Attach the pump to the body. Press the needle button.
+ Activating pump and settng initial basal rate. Please Wait.
+ Failed to activate, press Retry to try again.
+ New patch activated. %.2f Units remaining.
+ Press OK to return to main screen.
+
+ Are you sure that you want to deactivate the current patch?
+ Are you sure that you want to cancel the activation?
+ Are you sure? This action cannot be reversed!!
+ Press Next to deactivate or Cancel to return to main screen.
+ Deactivating patch. Please Wait.
+ Failed to deactivate, press Discard to forget patch.
+ Retract needle. Remove patch from body.
+ Remove pump base and dispose of used patch appropriately.
+ Press OK to return to main screen. Press Next to start activation of new patch.
+
+ Oops! Something went wrong, it seems there is already an activation in progress.
+ Press Next to resume the activation or Discard to reset the activation status.
+ Please wait, reading activation status from pump.
+
+
+ Serial Number
+ Enter the serial number of your pump base.
+ Alarm Settings
+ Select your preferred pump alarm settings.
+ Patch Expiration
+ When enabled, the patch will expire after 3 days, with a grace period of 8 hours after that.
+ Hourly Maximum Insulin
+ Specify the maximum units of insulin allowed per hour. If exceeded, the pump will suspend.
+ Daily Maximum Insulin
+ Specify the maximum units of insulin allowed per day. If exceeded, the pump will suspend.
+
+
+ Waiting for bolus end. Remaining %1$d sec.
+ Getting pump status
+ Getting bolus status
+ Getting temporary basal status
+ Setting user options
+
+
diff --git a/pump/medtrum/src/main/res/xml/pref_medtrum_pump.xml b/pump/medtrum/src/main/res/xml/pref_medtrum_pump.xml
new file mode 100644
index 0000000000..9195ccdf9f
--- /dev/null
+++ b/pump/medtrum/src/main/res/xml/pref_medtrum_pump.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pump/medtrum/src/test/java/info/nightscout/androidaps/ProfileStoreObject.kt b/pump/medtrum/src/test/java/info/nightscout/androidaps/ProfileStoreObject.kt
new file mode 100644
index 0000000000..31037b0f43
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/androidaps/ProfileStoreObject.kt
@@ -0,0 +1,107 @@
+package info.nightscout.androidaps
+
+import androidx.collection.ArrayMap
+import dagger.android.HasAndroidInjector
+import info.nightscout.core.extensions.pureProfileFromJson
+import info.nightscout.core.profile.ProfileSealed
+import info.nightscout.interfaces.Config
+import info.nightscout.interfaces.plugin.ActivePlugin
+import info.nightscout.interfaces.profile.ProfileStore
+import info.nightscout.interfaces.profile.PureProfile
+import info.nightscout.interfaces.utils.HardLimits
+import info.nightscout.interfaces.utils.JsonHelper
+import info.nightscout.rx.bus.RxBus
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.shared.interfaces.ResourceHelper
+import info.nightscout.shared.utils.DateUtil
+import org.json.JSONException
+import org.json.JSONObject
+import javax.inject.Inject
+
+class ProfileStoreObject(val injector: HasAndroidInjector, override val data: JSONObject, val dateUtil: DateUtil) : ProfileStore {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var activePlugin: ActivePlugin
+ @Inject lateinit var config: Config
+ @Inject lateinit var rh: ResourceHelper
+ @Inject lateinit var rxBus: RxBus
+ @Inject lateinit var hardLimits: HardLimits
+
+ init {
+ injector.androidInjector().inject(this)
+ }
+
+ private val cachedObjects = ArrayMap()
+
+ private fun storeUnits(): String? = JsonHelper.safeGetStringAllowNull(data, "units", null)
+
+ private fun getStore(): JSONObject? {
+ try {
+ if (data.has("store")) return data.getJSONObject("store")
+ } catch (e: JSONException) {
+ aapsLogger.error("Unhandled exception", e)
+ }
+ return null
+ }
+
+ override fun getStartDate(): Long {
+ val iso = JsonHelper.safeGetString(data, "startDate") ?: return 0
+ return try {
+ dateUtil.fromISODateString(iso)
+ } catch (e: Exception) {
+ 0
+ }
+ }
+
+ override fun getDefaultProfile(): PureProfile? = getDefaultProfileName()?.let { getSpecificProfile(it) }
+ override fun getDefaultProfileJson(): JSONObject? = getDefaultProfileName()?.let { getSpecificProfileJson(it) }
+
+ override fun getDefaultProfileName(): String? {
+ val defaultProfileName = data.optString("defaultProfile")
+ return if (defaultProfileName.isNotEmpty()) getStore()?.has(defaultProfileName)?.let { defaultProfileName } else null
+ }
+
+ override fun getProfileList(): ArrayList {
+ val ret = ArrayList()
+ getStore()?.keys()?.let { keys ->
+ while (keys.hasNext()) {
+ val profileName = keys.next() as String
+ ret.add(profileName)
+ }
+ }
+ return ret
+ }
+
+ @Synchronized
+ override fun getSpecificProfile(profileName: String): PureProfile? {
+ var profile: PureProfile? = null
+ val units = JsonHelper.safeGetStringAllowNull(data, "units", storeUnits())
+ getStore()?.let { store ->
+ if (store.has(profileName)) {
+ profile = cachedObjects[profileName]
+ if (profile == null) {
+ JsonHelper.safeGetJSONObject(store, profileName, null)?.let { profileObject ->
+ profile = pureProfileFromJson(profileObject, dateUtil, units)
+ profile?.let { cachedObjects[profileName] = profile }
+ }
+ }
+ }
+ }
+ return profile
+ }
+
+ private fun getSpecificProfileJson(profileName: String): JSONObject? {
+ getStore()?.let { store ->
+ if (store.has(profileName))
+ return JsonHelper.safeGetJSONObject(store, profileName, null)
+ }
+ return null
+ }
+
+ override val allProfilesValid: Boolean
+ get() = getProfileList()
+ .asSequence()
+ .map { profileName -> getSpecificProfile(profileName.toString()) }
+ .map { pureProfile -> pureProfile?.let { ProfileSealed.Pure(pureProfile).isValid("allProfilesValid", activePlugin.activePump, config, rh, rxBus, hardLimits, false) } }
+ .all { it?.isValid == true }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/androidaps/TestBase.kt b/pump/medtrum/src/test/java/info/nightscout/androidaps/TestBase.kt
new file mode 100644
index 0000000000..67aa52703d
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/androidaps/TestBase.kt
@@ -0,0 +1,38 @@
+package info.nightscout.androidaps
+
+import info.nightscout.rx.AapsSchedulers
+import info.nightscout.rx.TestAapsSchedulers
+import info.nightscout.rx.logging.AAPSLoggerTest
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.Mockito
+import org.mockito.junit.jupiter.MockitoExtension
+import org.mockito.junit.jupiter.MockitoSettings
+import org.mockito.quality.Strictness
+import java.util.Locale
+
+@Suppress("SpellCheckingInspection")
+@ExtendWith(MockitoExtension::class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+open class TestBase {
+
+ val aapsLogger = AAPSLoggerTest()
+ val aapsSchedulers: AapsSchedulers = TestAapsSchedulers()
+
+ @BeforeEach
+ fun setupLocale() {
+ Locale.setDefault(Locale.ENGLISH)
+ System.setProperty("disableFirebase", "true")
+ }
+
+ // Workaround for Kotlin nullability.
+ // https://medium.com/@elye.project/befriending-kotlin-and-mockito-1c2e7b0ef791
+ // https://stackoverflow.com/questions/30305217/is-it-possible-to-use-mockito-in-kotlin
+ fun anyObject(): T {
+ Mockito.any()
+ return uninitialized()
+ }
+
+ @Suppress("Unchecked_Cast")
+ fun uninitialized(): T = null as T
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/androidaps/TestBaseWithProfile.kt b/pump/medtrum/src/test/java/info/nightscout/androidaps/TestBaseWithProfile.kt
new file mode 100644
index 0000000000..ef46cbe7e0
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/androidaps/TestBaseWithProfile.kt
@@ -0,0 +1,177 @@
+package info.nightscout.androidaps
+
+import android.content.Context
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.core.extensions.pureProfileFromJson
+import info.nightscout.core.profile.ProfileSealed
+import info.nightscout.core.utils.fabric.FabricPrivacy
+import info.nightscout.database.entities.EffectiveProfileSwitch
+import info.nightscout.database.entities.embedments.InsulinConfiguration
+import info.nightscout.interfaces.Config
+import info.nightscout.interfaces.iob.IobCobCalculator
+import info.nightscout.interfaces.plugin.ActivePlugin
+import info.nightscout.interfaces.profile.ProfileFunction
+import info.nightscout.interfaces.profile.ProfileStore
+import info.nightscout.rx.bus.RxBus
+import info.nightscout.shared.interfaces.ResourceHelper
+import info.nightscout.shared.utils.DateUtil
+import org.json.JSONObject
+import org.junit.jupiter.api.BeforeEach
+import org.mockito.ArgumentMatchers.anyDouble
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.invocation.InvocationOnMock
+
+@Suppress("SpellCheckingInspection")
+open class TestBaseWithProfile : TestBase() {
+
+ @Mock lateinit var activePluginProvider: ActivePlugin
+ @Mock lateinit var rh: ResourceHelper
+ @Mock lateinit var iobCobCalculator: IobCobCalculator
+ @Mock lateinit var fabricPrivacy: FabricPrivacy
+ @Mock lateinit var profileFunction: ProfileFunction
+ @Mock lateinit var config: Config
+ @Mock lateinit var context: Context
+
+ lateinit var dateUtil: DateUtil
+ val rxBus = RxBus(aapsSchedulers, aapsLogger)
+
+ val profileInjector = HasAndroidInjector { AndroidInjector { } }
+
+ private lateinit var validProfileJSON: String
+ lateinit var validProfile: ProfileSealed.Pure
+ lateinit var effectiveProfileSwitch: EffectiveProfileSwitch
+
+ @Suppress("PropertyName") val TESTPROFILENAME = "someProfile"
+
+ @BeforeEach
+ fun prepareMock() {
+ validProfileJSON = "{\"dia\":\"5\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"3\"}," +
+ "{\"time\":\"2:00\",\"value\":\"3.4\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4.5\"}]," +
+ "\"target_high\":[{\"time\":\"00:00\",\"value\":\"7\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}"
+ dateUtil = Mockito.spy(DateUtil(context))
+ `when`(dateUtil.now()).thenReturn(1656358822000)
+ validProfile = ProfileSealed.Pure(pureProfileFromJson(JSONObject(validProfileJSON), dateUtil)!!)
+ effectiveProfileSwitch = EffectiveProfileSwitch(
+ timestamp = dateUtil.now(),
+ basalBlocks = validProfile.basalBlocks,
+ isfBlocks = validProfile.isfBlocks,
+ icBlocks = validProfile.icBlocks,
+ targetBlocks = validProfile.targetBlocks,
+ glucoseUnit = EffectiveProfileSwitch.GlucoseUnit.MMOL,
+ originalProfileName = "",
+ originalCustomizedName = "",
+ originalTimeshift = 0,
+ originalPercentage = 100,
+ originalDuration = 0,
+ originalEnd = 0,
+ insulinConfiguration = InsulinConfiguration("", 0, 0)
+ )
+
+ Mockito.doAnswer { invocation: InvocationOnMock ->
+ val string = invocation.getArgument(0)
+ val arg1 = invocation.getArgument(1)
+ String.format(rh.gs(string), arg1)
+ }.`when`(rh).gs(anyInt(), anyInt())
+
+ Mockito.doAnswer { invocation: InvocationOnMock ->
+ val string = invocation.getArgument(0)
+ val arg1 = invocation.getArgument(1)
+ String.format(rh.gs(string), arg1)
+ }.`when`(rh).gs(anyInt(), anyDouble())
+
+ Mockito.doAnswer { invocation: InvocationOnMock ->
+ val string = invocation.getArgument(0)
+ val arg1 = invocation.getArgument(1)
+ String.format(rh.gs(string), arg1)
+ }.`when`(rh).gs(anyInt(), anyString())
+
+ Mockito.doAnswer { invocation: InvocationOnMock ->
+ val string = invocation.getArgument(0)
+ val arg1 = invocation.getArgument(1)
+ val arg2 = invocation.getArgument(2)
+ String.format(rh.gs(string), arg1, arg2)
+ }.`when`(rh).gs(anyInt(), anyString(), anyString())
+
+ Mockito.doAnswer { invocation: InvocationOnMock ->
+ val string = invocation.getArgument(0)
+ val arg1 = invocation.getArgument(1)
+ val arg2 = invocation.getArgument(2)
+ String.format(rh.gs(string), arg1, arg2)
+ }.`when`(rh).gs(anyInt(), anyString(), anyInt())
+
+ Mockito.doAnswer { invocation: InvocationOnMock ->
+ val string = invocation.getArgument(0)
+ val arg1 = invocation.getArgument(1)
+ val arg2 = invocation.getArgument(2)
+ String.format(rh.gs(string), arg1, arg2)
+ }.`when`(rh).gs(anyInt(), anyDouble(), anyString())
+
+ Mockito.doAnswer { invocation: InvocationOnMock ->
+ val string = invocation.getArgument(0)
+ val arg1 = invocation.getArgument(1)
+ val arg2 = invocation.getArgument(2)
+ String.format(rh.gs(string), arg1, arg2)
+ }.`when`(rh).gs(anyInt(), anyDouble(), anyInt())
+
+ Mockito.doAnswer { invocation: InvocationOnMock ->
+ val string = invocation.getArgument(0)
+ val arg1 = invocation.getArgument(1)
+ val arg2 = invocation.getArgument(2)
+ String.format(rh.gs(string), arg1, arg2)
+ }.`when`(rh).gs(anyInt(), anyInt(), anyInt())
+
+ Mockito.doAnswer { invocation: InvocationOnMock ->
+ val string = invocation.getArgument(0)
+ val arg1 = invocation.getArgument(1)
+ val arg2 = invocation.getArgument(2)
+ String.format(rh.gs(string), arg1, arg2)
+ }.`when`(rh).gs(anyInt(), anyInt(), anyString())
+
+ Mockito.doAnswer { invocation: InvocationOnMock ->
+ val string = invocation.getArgument(0)
+ val arg1 = invocation.getArgument(1)
+ val arg2 = invocation.getArgument(2)
+ val arg3 = invocation.getArgument(3)
+ String.format(rh.gs(string), arg1, arg2, arg3)
+ }.`when`(rh).gs(anyInt(), anyInt(), anyInt(), anyString())
+
+ Mockito.doAnswer { invocation: InvocationOnMock ->
+ val string = invocation.getArgument(0)
+ val arg1 = invocation.getArgument(1)
+ val arg2 = invocation.getArgument(2)
+ val arg3 = invocation.getArgument(3)
+ String.format(rh.gs(string), arg1, arg2, arg3)
+ }.`when`(rh).gs(anyInt(), anyInt(), anyString(), anyString())
+
+ Mockito.doAnswer { invocation: InvocationOnMock ->
+ val string = invocation.getArgument(0)
+ val arg1 = invocation.getArgument(1)
+ val arg2 = invocation.getArgument(2)
+ val arg3 = invocation.getArgument(3)
+ String.format(rh.gs(string), arg1, arg2, arg3)
+ }.`when`(rh).gs(anyInt(), anyDouble(), anyInt(), anyString())
+
+ Mockito.doAnswer { invocation: InvocationOnMock ->
+ val string = invocation.getArgument(0)
+ val arg1 = invocation.getArgument(1)
+ val arg2 = invocation.getArgument(2)
+ val arg3 = invocation.getArgument(3)
+ String.format(rh.gs(string), arg1, arg2, arg3)
+ }.`when`(rh).gs(anyInt(), anyString(), anyInt(), anyString())
+
+ }
+
+ fun getValidProfileStore(): ProfileStore {
+ val json = JSONObject()
+ val store = JSONObject()
+ store.put(TESTPROFILENAME, JSONObject(validProfileJSON))
+ json.put("defaultProfile", TESTPROFILENAME)
+ json.put("store", store)
+ return ProfileStoreObject(profileInjector, json, dateUtil)
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/MedtrumPumpTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/MedtrumPumpTest.kt
new file mode 100644
index 0000000000..ed9771966c
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/MedtrumPumpTest.kt
@@ -0,0 +1,104 @@
+package info.nightscout.pump.medtrum
+
+import info.nightscout.core.extensions.pureProfileFromJson
+import info.nightscout.core.profile.ProfileSealed
+import org.json.JSONObject
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+import java.time.LocalDate
+import java.time.LocalTime
+import java.time.ZoneId
+
+class MedtrumPumpTest : MedtrumTestBase() {
+
+ @Test fun buildMedtrumProfileArrayGivenProfileWhenValuesSetThenReturnCorrectByteArray() {
+ // Inputs
+ // Basal profile with 7 elements:
+ // 00:00 : 2.1
+ // 04:00 : 1.9
+ // 06:00 : 1.7
+ // 08:00 : 1.5
+ // 16:00 : 1.6
+ // 21:00 : 1.7
+ // 23:00 : 2
+ val profileJSON = "{\"dia\":\"5\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\"," +
+ "\"sens\":[{\"time\":\"00:00\",\"value\":\"3\"},{\"time\":\"02:00\",\"value\":\"3.4\"}],\"timezone\":\"UTC\"," +
+ "\"basal\":[{\"time\":\"00:00\",\"value\":\"2.1\"},{\"time\":\"04:00\",\"value\":\"1.9\"},{\"time\":\"06:00\",\"value\":\"1.7\"}," +
+ "{\"time\":\"08:00\",\"value\":\"1.5\"},{\"time\":\"16:00\",\"value\":\"1.6\"},{\"time\":\"21:00\",\"value\":\"1.7\"},{\"time\":\"23:00\",\"value\":\"2\"}]," +
+ "\"target_low\":[{\"time\":\"00:00\",\"value\":\"4.5\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"7\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}"
+ val profile = ProfileSealed.Pure(pureProfileFromJson(JSONObject(profileJSON), dateUtil)!!)
+
+ // Call
+ val result = medtrumPump.buildMedtrumProfileArray(profile)
+
+ // Expected values
+ val expectedByteArray = byteArrayOf(7, 0, -96, 2, -16, 96, 2, 104, 33, 2, -32, -31, 1, -64, 3, 2, -20, 36, 2, 100, -123, 2)
+ assertEquals(expectedByteArray.contentToString(), result?.contentToString())
+ }
+
+ @Test fun buildMedtrumProfileArrayGiveProfileWhenValuesTooHighThenReturnNull() {
+ // Inputs
+ // Basal profile with 1 element:
+ // 00:00 : 600
+ val profileJSON = "{\"dia\":\"5\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\"," +
+ "\"sens\":[{\"time\":\"00:00\",\"value\":\"3\"},{\"time\":\"02:00\",\"value\":\"3.4\"}],\"timezone\":\"UTC\"," +
+ "\"basal\":[{\"time\":\"00:00\",\"value\":\"600\"}]," +
+ "\"target_low\":[{\"time\":\"00:00\",\"value\":\"4.5\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"7\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}"
+ val profile = ProfileSealed.Pure(pureProfileFromJson(JSONObject(profileJSON), dateUtil)!!)
+
+ // Call
+ val result = medtrumPump.buildMedtrumProfileArray(profile)
+
+ // Expected values
+ assertNull(result)
+ }
+
+ @Test fun getCurrentHourlyBasalFromMedtrumProfileArrayGivenProfileWhenValuesSetThenReturnCorrectValue() {
+ // Inputs
+ // Basal profile with 7 elements:
+ // 00:00 : 2.1
+ // 04:00 : 1.9
+ // 06:00 : 1.7
+ // 08:00 : 1.5
+ // 16:00 : 1.6
+ // 21:00 : 1.7
+ // 23:00 : 2
+ val profileJSON = "{\"dia\":\"5\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\"," +
+ "\"sens\":[{\"time\":\"00:00\",\"value\":\"3\"},{\"time\":\"02:00\",\"value\":\"3.4\"}],\"timezone\":\"UTC\"," +
+ "\"basal\":[{\"time\":\"00:00\",\"value\":\"2.1\"},{\"time\":\"04:00\",\"value\":\"1.9\"},{\"time\":\"06:00\",\"value\":\"1.7\"}," +
+ "{\"time\":\"08:00\",\"value\":\"1.5\"},{\"time\":\"16:00\",\"value\":\"1.6\"},{\"time\":\"21:00\",\"value\":\"1.7\"},{\"time\":\"23:00\",\"value\":\"2\"}]," +
+ "\"target_low\":[{\"time\":\"00:00\",\"value\":\"4.5\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"7\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}"
+ val profile = ProfileSealed.Pure(pureProfileFromJson(JSONObject(profileJSON), dateUtil)!!)
+ val profileArray = medtrumPump.buildMedtrumProfileArray(profile)
+
+ val localDate = LocalDate.of(2023, 1, 1)
+
+ // For 03:59
+ val localTime0399 = LocalTime.of(3, 59)
+ val zonedDateTime0399 = localDate.atTime(localTime0399).atZone(ZoneId.systemDefault())
+ val time0399 = zonedDateTime0399.toInstant().toEpochMilli()
+ val result = medtrumPump.getHourlyBasalFromMedtrumProfileArray(profileArray!!, time0399)
+ assertEquals(2.1, result, 0.01)
+
+ // For 22:30
+ val localTime2230 = LocalTime.of(22, 30)
+ val zonedDateTime2230 = localDate.atTime(localTime2230).atZone(ZoneId.systemDefault())
+ val time2230 = zonedDateTime2230.toInstant().toEpochMilli()
+ val result1 = medtrumPump.getHourlyBasalFromMedtrumProfileArray(profileArray!!, time2230)
+ assertEquals(1.7, result1, 0.01)
+
+ // For 23:59
+ val localTime2359 = LocalTime.of(23, 59)
+ val zonedDateTime2359 = localDate.atTime(localTime2359).atZone(ZoneId.systemDefault())
+ val time2359 = zonedDateTime2359.toInstant().toEpochMilli()
+ val result2 = medtrumPump.getHourlyBasalFromMedtrumProfileArray(profileArray!!, time2359)
+ assertEquals(2.0, result2, 0.01)
+
+ // For 00:00
+ val localTime0000 = LocalTime.of(0, 0)
+ val zonedDateTime0000 = localDate.atTime(localTime0000).atZone(ZoneId.systemDefault())
+ val time0000 = zonedDateTime0000.toInstant().toEpochMilli()
+ val result3 = medtrumPump.getHourlyBasalFromMedtrumProfileArray(profileArray!!, time0000)
+ assertEquals(2.1, result3, 0.01)
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/MedtrumTestBase.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/MedtrumTestBase.kt
new file mode 100644
index 0000000000..63d298ce6b
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/MedtrumTestBase.kt
@@ -0,0 +1,26 @@
+package info.nightscout.pump.medtrum
+
+import info.nightscout.androidaps.TestBaseWithProfile
+import info.nightscout.shared.sharedPreferences.SP
+import info.nightscout.interfaces.profile.Instantiator
+import info.nightscout.interfaces.pump.PumpSync
+import info.nightscout.interfaces.pump.TemporaryBasalStorage
+import info.nightscout.interfaces.stats.TddCalculator
+import org.junit.jupiter.api.BeforeEach
+import org.mockito.Mock
+
+open class MedtrumTestBase: TestBaseWithProfile() {
+
+ @Mock lateinit var sp: SP
+ @Mock lateinit var instantiator: Instantiator
+ @Mock lateinit var tddCalculator: TddCalculator
+ @Mock lateinit var pumpSync: PumpSync
+ @Mock lateinit var temporaryBasalStorage: TemporaryBasalStorage
+
+ lateinit var medtrumPump: MedtrumPump
+
+ @BeforeEach
+ fun setup() {
+ medtrumPump = MedtrumPump(aapsLogger, rh, sp, dateUtil, pumpSync, temporaryBasalStorage)
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/WriteCommandPacketsTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/WriteCommandPacketsTest.kt
new file mode 100644
index 0000000000..67ac34bcaf
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/WriteCommandPacketsTest.kt
@@ -0,0 +1,40 @@
+package info.nightscout.pump.medtrum.comm
+
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class WriteCommandPacketsTest {
+
+ @Test
+ fun Given14LongCommandExpectOnePacket() {
+ val input = byteArrayOf(5, 2, 0, 0, 0, 0, -21, 57, -122, -56)
+ val expect = byteArrayOf(14, 5, 0, 0, 2, 0, 0, 0, 0, -21, 57, -122, -56, -93, 0)
+ val sequence = 0
+ val cmdPackets = WriteCommandPackets(input, sequence)
+ val output = cmdPackets.getNextPacket()
+
+ assertEquals(expect.contentToString(), output.contentToString())
+ }
+
+ @Test
+ fun Given41LongCommandExpectThreePackets() {
+ val input = byteArrayOf(18, 0, 12, 0, 3, 0, 1, 30, 32, 3, 16, 14, 0, 0, 1, 7, 0, -96, 2, -16, 96, 2, 104, 33, 2, -32, -31, 1, -64, 3, 2, -20, 36, 2, 100, -123, 2)
+ val expect1 = byteArrayOf(41, 18, 0, 1, 0, 12, 0, 3, 0, 1, 30, 32, 3, 16, 14, 0, 0, 1, 7, -121)
+ val expect2 = byteArrayOf(41, 18, 0, 2, 0, -96, 2, -16, 96, 2, 104, 33, 2, -32, -31, 1, -64, 3, 2, -3)
+ val expect3 = byteArrayOf(41, 18, 0, 3, -20, 36, 2, 100, -123, 2, -125, -89)
+
+ val sequence = 0
+ val cmdPackets = WriteCommandPackets(input, sequence)
+ val output1 = cmdPackets.getNextPacket()
+ val output2 = cmdPackets.getNextPacket()
+ val output3 = cmdPackets.getNextPacket()
+ val output4 = cmdPackets.getNextPacket()
+
+
+ assertEquals(expect1.contentToString(), output1.contentToString())
+ assertEquals(expect2.contentToString(), output2.contentToString())
+ assertEquals(expect3.contentToString(), output3.contentToString())
+ assertNull(output4)
+ assertEquals(true, cmdPackets.allPacketsConsumed())
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/ActivatePacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/ActivatePacketTest.kt
new file mode 100644
index 0000000000..56c82c95a4
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/ActivatePacketTest.kt
@@ -0,0 +1,89 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import info.nightscout.pump.medtrum.comm.enums.AlarmSetting
+import info.nightscout.pump.medtrum.comm.enums.BasalType
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class ActivatePacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is ActivatePacket) {
+ it.aapsLogger = aapsLogger
+ it.medtrumPump = medtrumPump
+ it.tddCalculator = tddCalculator
+ it.pumpSync = pumpSync
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenValuesSetThenReturnCorrectByteArray() {
+ // Inputs
+ medtrumPump.desiredPatchExpiration = true
+ medtrumPump.desiredAlarmSetting = AlarmSetting.BEEP_ONLY
+ medtrumPump.desiredDailyMaxInsulin = 40
+ medtrumPump.desiredDailyMaxInsulin = 180
+
+ val basalProfile = byteArrayOf(3, 16, 14, 0, 0, 1, 2, 12, 12, 12)
+
+ // Call
+ val packet = ActivatePacket(packetInjector, basalProfile)
+ val result = packet.getRequest()
+
+ // Expected values
+ val expectedByteArray = byteArrayOf(18, 0, 12, 1, 6, 0, 0, 30, 32, 3, 16, 14, 0, 0, 1, 3, 16, 14, 0, 0, 1, 2, 12, 12, 12)
+ assertEquals(expectedByteArray.contentToString(), result.contentToString())
+ }
+
+ @Test fun handleResponseGivenPacketWhenValuesSetThenReturnCorrectValues() {
+ // Inputs
+ medtrumPump.desiredPatchExpiration = true
+ medtrumPump.desiredAlarmSetting = AlarmSetting.BEEP_ONLY
+ medtrumPump.desiredDailyMaxInsulin = 40
+ medtrumPump.desiredDailyMaxInsulin = 180
+
+ val basalProfile = byteArrayOf(3, 16, 14, 0, 0, 1, 2, 12, 12, 12)
+ val response = byteArrayOf(26, 18, 19, 1, 0, 0, 41, 0, 0, 0, -104, 91, 28, 17, 1, 30, 0, 1, 0, 41, 0, -104, 91, 28, 17)
+
+ // Call
+ val packet = ActivatePacket(packetInjector, basalProfile)
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ val expectedPatchId = 41L
+ val expectedTime = 1675605528000L
+ val exptectedBasalType = BasalType.STANDARD
+ val expectedBasalRate = 1.5
+ val expectedBasalSequence = 1
+ val expectedBasalPatchId = 41L
+ val expectedBasalStart = 1675605528000L
+
+ assertEquals(true, result)
+ assertEquals(expectedPatchId, medtrumPump.patchId)
+ assertEquals(expectedTime, medtrumPump.lastTimeReceivedFromPump)
+ assertEquals(exptectedBasalType, medtrumPump.lastBasalType)
+ assertEquals(expectedBasalRate, medtrumPump.lastBasalRate, 0.01)
+ assertEquals(expectedBasalSequence, medtrumPump.lastBasalSequence)
+ assertEquals(expectedBasalPatchId, medtrumPump.lastBasalPatchId)
+ assertEquals(expectedBasalStart, medtrumPump.lastBasalStartTime)
+ assertEquals(basalProfile, medtrumPump.actualBasalProfile)
+ }
+
+ @Test fun handleResponseGivenResponseWhenMessageTooShortThenResultFalse() {
+ // Inputs
+ val response = byteArrayOf(26, 18, 19, 1, 0, 0, 41, 0, 0, 0, -104, 91, 28, 17, 1, 30, 0, 1, 0, 41, 0, -104, 91, 28)
+
+ // Call
+ val packet = ActivatePacket(packetInjector, byteArrayOf())
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ assertFalse(result)
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/AuthorizePacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/AuthorizePacketTest.kt
new file mode 100644
index 0000000000..697d439e25
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/AuthorizePacketTest.kt
@@ -0,0 +1,81 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumPump
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import info.nightscout.pump.medtrum.extension.toByteArray
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class AuthorizePacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is AuthorizePacket) {
+ it.aapsLogger = aapsLogger
+ it.medtrumPump = medtrumPump
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketAndSNWhenCalledThenReturnAuthorizePacket() {
+ // Inputs
+ val opCode = 5
+ val _pumpSN = MedtrumPump::class.java.getDeclaredField("_pumpSN")
+ _pumpSN.isAccessible = true
+ _pumpSN.setLong(medtrumPump, 2859923929)
+ medtrumPump.patchSessionToken = 667
+
+ // Call
+ val packet = AuthorizePacket(packetInjector)
+ val result = packet.getRequest()
+
+ // Expected values
+ val key = 3364239851
+ val type = 2
+ val expectedByteArray = byteArrayOf(opCode.toByte()) + type.toByte() + medtrumPump.patchSessionToken.toByteArray(4) + key.toByteArray(4)
+ assertEquals(10, result.size)
+ assertEquals(expectedByteArray.contentToString(), result.contentToString())
+ }
+
+ @Test fun handleResponseGivenResponseWhenMessageIsCorrectLengthThenResultTrue() {
+ // Inputs
+ val opCode = 5
+ val responseCode = 0
+ val deviceType = 80
+ val swVerX = 12
+ val swVerY = 1
+ val swVerZ = 3
+
+ // Call
+ val response = byteArrayOf(0) + opCode.toByte() + 0.toByteArray(2) + responseCode.toByteArray(2) + 0.toByte() + deviceType.toByte() + swVerX.toByte() + swVerY.toByte() + swVerZ.toByte()
+ val packet = AuthorizePacket(packetInjector)
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ val swString = "$swVerX.$swVerY.$swVerZ"
+ assertTrue(result)
+ assertFalse(packet.failed)
+ assertEquals(deviceType, medtrumPump.deviceType)
+ assertEquals(swString, medtrumPump.swVersion)
+ }
+
+ @Test fun handleResponseGivenResponseWhenMessageTooShortThenResultFalse() {
+ // Inputs
+ val opCode = 5
+ val responseCode = 0
+
+ // Call
+ val response = byteArrayOf(0) + opCode.toByte() + 0.toByteArray(2) + responseCode.toByteArray(2)
+ val packet = AuthorizePacket(packetInjector)
+ packet.opCode = opCode.toByte()
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ assertFalse(result)
+ assertTrue(packet.failed)
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/CancelBolusPacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/CancelBolusPacketTest.kt
new file mode 100644
index 0000000000..fcca4a53fb
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/CancelBolusPacketTest.kt
@@ -0,0 +1,34 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class CancelBolusPacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is MedtrumPacket) {
+ it.aapsLogger = aapsLogger
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val opCode = 20
+
+ // Call
+ val packet = CancelBolusPacket(packetInjector)
+ val result = packet.getRequest()
+
+ // Expected values
+ val expectedByteArray = byteArrayOf(opCode.toByte()) + 1.toByte()
+ assertEquals(2, result.size)
+ assertEquals(expectedByteArray.contentToString(), result.contentToString())
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/CancelTempBasalPacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/CancelTempBasalPacketTest.kt
new file mode 100644
index 0000000000..ca99d4278b
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/CancelTempBasalPacketTest.kt
@@ -0,0 +1,72 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import info.nightscout.pump.medtrum.comm.enums.BasalType
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class CancelTempBasalPacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is CancelTempBasalPacket) {
+ it.aapsLogger = aapsLogger
+ it.medtrumPump = medtrumPump
+ it.dateUtil = dateUtil
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val opCode = 25
+
+ // Call
+ val packet = CancelTempBasalPacket(packetInjector)
+ val result = packet.getRequest()
+
+ // Expected values
+ assertEquals(1, result.size)
+ assertEquals(opCode.toByte(), result[0])
+ }
+
+ @Test fun handleResponseGivenPacketWhenValuesSetThenReturnCorrectValues() {
+ // Inputs
+ val repsonse = byteArrayOf(18, 25, 16, 0, 0, 0, 1, 22, 0, 3, 0, -110, 0, -32, -18, 88, 17)
+
+ // Call
+ val packet = CancelTempBasalPacket(packetInjector)
+ val result = packet.handleResponse(repsonse)
+
+ // Expected values
+ val expectedBasalType = BasalType.STANDARD
+ val expectedBasalRate = 1.1
+ val expectedBasalSequence = 3
+ val expectedStartTime = 1679575392000L
+ val expectedPatchId = 146L
+
+ assertTrue(result)
+ assertEquals(expectedBasalType, medtrumPump.lastBasalType)
+ assertEquals(expectedBasalRate, medtrumPump.lastBasalRate, 0.01)
+ assertEquals(expectedBasalSequence, medtrumPump.lastBasalSequence)
+ assertEquals(expectedStartTime, medtrumPump.lastBasalStartTime)
+ assertEquals(expectedPatchId, medtrumPump.lastBasalPatchId)
+ }
+
+ @Test fun handleResponseGivenResponseWhenMessageTooShortThenResultFalse() {
+ // Inputs
+ val response = byteArrayOf(18, 25, 16, 0, 0, 0, 1, 22, 0, 3, 0, -110, 0, -32, -18, 88)
+
+ // Call
+ val packet = CancelTempBasalPacket(packetInjector)
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ assertFalse(result)
+ assertTrue(packet.failed)
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/ClearPumpAlarmPacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/ClearPumpAlarmPacketTest.kt
new file mode 100644
index 0000000000..b1afad48d2
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/ClearPumpAlarmPacketTest.kt
@@ -0,0 +1,36 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import info.nightscout.pump.medtrum.extension.toInt
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class ClearPumpAlarmPacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is MedtrumPacket) {
+ it.aapsLogger = aapsLogger
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val opCode = 115
+ val clearCode = 4
+
+ // Call
+ val packet = ClearPumpAlarmPacket(packetInjector, clearCode)
+ val result = packet.getRequest()
+
+ // Expected values
+ assertEquals(3, result.size)
+ assertEquals(opCode.toByte(), result[0])
+ assertEquals(clearCode, result.copyOfRange(1, 3).toInt())
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/GetDeviceTypePacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/GetDeviceTypePacketTest.kt
new file mode 100644
index 0000000000..73663e4ee0
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/GetDeviceTypePacketTest.kt
@@ -0,0 +1,73 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import info.nightscout.pump.medtrum.extension.toByteArray
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class GetDeviceTypePacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is MedtrumPacket) {
+ it.aapsLogger = aapsLogger
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val opCode = 6
+
+ // Call
+ val packet = GetDeviceTypePacket(packetInjector)
+ val result = packet.getRequest()
+
+ // Expected values
+ assertEquals(1, result.size)
+ assertEquals(opCode.toByte(), result[0])
+ }
+
+ @Test fun handleResponseGivenResponseWhenMessageIsCorrectLengthThenResultTrue() {
+ // Inputs
+ val opCode = 6
+ val responseCode = 0
+ val deviceType = 80
+ val deviceSN: Long = 12345678
+ val response = byteArrayOf(0) + opCode.toByte() + 0.toByteArray(2) + responseCode.toByteArray(2) + deviceType.toByte() + deviceSN.toByteArray(4)
+
+ // Call
+ val packet = GetDeviceTypePacket(packetInjector)
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ assertEquals(true, result)
+ assertEquals(false, packet.failed)
+ assertEquals(deviceType, packet.deviceType)
+ assertEquals(deviceSN, packet.deviceSN)
+
+ }
+
+ @Test fun handleResponseGivenResponseWhenMessageTooShortThenResultFalse() {
+ // Inputs
+ val opCode = 6
+ val responseCode = 0
+ val deviceType = 80
+ val deviceSN: Long = 12345678
+ val response = byteArrayOf(0) + opCode.toByte() + 0.toByteArray(2) + responseCode.toByteArray(2) + deviceType.toByte() + deviceSN.toByteArray(4)
+
+ // Call
+ val packet = GetDeviceTypePacket(packetInjector)
+ val result = packet.handleResponse(response.sliceArray(0..response.size - 2))
+
+ // Expected values
+ assertEquals(false, result)
+ assertEquals(true, packet.failed)
+ assertEquals(0, packet.deviceType)
+ assertEquals(0, packet.deviceSN)
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/GetRecordPacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/GetRecordPacketTest.kt
new file mode 100644
index 0000000000..70b4405f58
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/GetRecordPacketTest.kt
@@ -0,0 +1,62 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import info.nightscout.pump.medtrum.extension.toByteArray
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class GetRecordPacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is GetRecordPacket) {
+ it.aapsLogger = aapsLogger
+ it.medtrumPump = medtrumPump
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val recordIndex = 4
+ medtrumPump.patchId = 146
+
+ // Call
+ val packet = GetRecordPacket(packetInjector, recordIndex)
+ val result = packet.getRequest()
+
+ // Expected values
+ val expected = byteArrayOf(99, 4, 0, -110, 0)
+ assertEquals(expected.contentToString(), result.contentToString())
+ }
+
+ @Test fun handleResponseGivenPacketWhenValuesSetThenReturnCorrectValues() {
+ // Inputs
+ val data = byteArrayOf(35, 99, 9, 1, 0, 0, -86, 28, 2, -1, -5, -40, -27, -18, 14, 0, -64, 1, -91, -20, -82, 17, -91, -20, -82, 17, 1, 0, 26, 0, 0, 0, -102, 0, -48)
+
+ // Call
+ val packet = GetRecordPacket(packetInjector, 0)
+ val result = packet.handleResponse(data)
+
+ // Expected values
+ assertEquals(true, result)
+ assertEquals(false, packet.failed)
+ }
+
+ @Test fun handleResponseGivenResponseWhenMessageTooShortThenResultFalse() {
+ // Inputs
+ val data = byteArrayOf(35, 99, 9, 1, 0, 0, -86, 28, 2, -1, -5, -40, -27, -18, 14, 0, -64)
+
+ // Call
+ val packet = GetRecordPacket(packetInjector, 0)
+ val result = packet.handleResponse(data)
+
+ // Expected values
+ assertEquals(false, result)
+ assertEquals(true, packet.failed)
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/GetTimePacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/GetTimePacketTest.kt
new file mode 100644
index 0000000000..36c9751bb8
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/GetTimePacketTest.kt
@@ -0,0 +1,70 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import info.nightscout.pump.medtrum.extension.toByteArray
+import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class GetTimePacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is GetTimePacket) {
+ it.aapsLogger = aapsLogger
+ it.medtrumPump = medtrumPump
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val opCode = 11
+
+ // Call
+ val packet = GetTimePacket(packetInjector)
+ val result = packet.getRequest()
+
+ // Expected values
+ assertEquals(1, result.size)
+ assertEquals(opCode.toByte(), result[0])
+ }
+
+ @Test fun handleResponseGivenResponseWhenMessageIsCorrectLengthThenResultTrue() {
+ // Inputs
+ val opCode = 11
+ val responseCode = 0
+ val time: Long = 1234567890
+ val response = byteArrayOf(0) + opCode.toByte() + 0.toByteArray(2) + responseCode.toByteArray(2) + time.toByteArray(4)
+
+ // Call
+ val packet = GetTimePacket(packetInjector)
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ assertEquals(true, result)
+ assertEquals(false, packet.failed)
+ assertEquals(MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(time), medtrumPump.lastTimeReceivedFromPump)
+ }
+
+ @Test fun handleResponseGivenResponseWhenMessageTooShortThenResultFalse() {
+ // Inputs
+ val opCode = 11
+ val responseCode = 0
+ val time: Long = 1234567890
+ val response = byteArrayOf(0) + opCode.toByte() + 0.toByteArray(2) + responseCode.toByteArray(2) + time.toByteArray(4)
+
+ // Call
+ val packet = GetTimePacket(packetInjector)
+ val result = packet.handleResponse(response.sliceArray(0..response.size - 2))
+
+ // Expected values
+ assertEquals(false, result)
+ assertEquals(true, packet.failed)
+ assertEquals(0, medtrumPump.lastTimeReceivedFromPump)
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/MedtrumPacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/MedtrumPacketTest.kt
new file mode 100644
index 0000000000..0c6bf53ac9
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/MedtrumPacketTest.kt
@@ -0,0 +1,115 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import info.nightscout.pump.medtrum.extension.toByteArray
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class MedtrumPacketTest : MedtrumTestBase() {
+
+ /** Test base behavoir of the medtrum packet */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is MedtrumPacket) {
+ it.aapsLogger = aapsLogger
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val opCode = 1
+
+ // Call
+ val packet = MedtrumPacket(packetInjector)
+ packet.opCode = opCode.toByte()
+ val result = packet.getRequest()
+
+ // Expected values
+ assertEquals(result.size, 1)
+ assertEquals(result[0], opCode.toByte())
+ }
+
+ @Test fun handleResponseGivenResponseWhenOpcodeIsCorrectThenResultTrue() {
+ // Inputs
+ val opCode = 1
+ val responseCode = 0
+ val response = byteArrayOf(0) + opCode.toByte() + 0x0 + 0x0 + responseCode.toByteArray(2)
+
+ // Call
+ val packet = MedtrumPacket(packetInjector)
+ packet.opCode = opCode.toByte()
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ assertEquals(result, true)
+ assertEquals(packet.failed, false)
+ }
+
+ @Test fun handleResponseGivenRepsonseWhenOpcodeIsIncorrectThenResultFalse() {
+ // Inputs
+ val opCode = 1
+ val responseCode = 0
+ val response = byteArrayOf(0) + (opCode + 1).toByte() + 0x0 + 0x0 + responseCode.toByteArray(2)
+
+ // Call
+ val packet = MedtrumPacket(packetInjector)
+ packet.opCode = opCode.toByte()
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ assertEquals(result, false)
+ assertEquals(packet.failed, true)
+ }
+
+ @Test fun handleResponseGivenResponseWhenResponseCodeIsWaitingThenResultFalse() {
+ // Inputs
+ val opCode = 1
+ val responseCode = 16384
+ val response = byteArrayOf(0) + opCode.toByte() + 0x0 + 0x0 + responseCode.toByteArray(2)
+
+ // Call
+ val packet = MedtrumPacket(packetInjector)
+ packet.opCode = opCode.toByte()
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ assertEquals(result, false)
+ assertEquals(packet.failed, false)
+ }
+
+ @Test fun handleResponseGivenResponseWhenRepsonseCodeIsErrorThenResultFalse() {
+ // Inputs
+ val opCode = 1
+ val responseCode = 1
+ val response = byteArrayOf(0) + opCode.toByte() + 0.toByteArray(2) + responseCode.toByteArray(2)
+
+ // Call
+ val packet = MedtrumPacket(packetInjector)
+ packet.opCode = opCode.toByte()
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ assertEquals(false, result)
+ assertEquals(true, packet.failed)
+ }
+
+ @Test fun handleResponseGivenResponseWhenMessageTooShortThenResultFalse() {
+ // Inputs
+ val opCode = 1
+ val responseCode = 0
+ val response = byteArrayOf(0) + opCode.toByte() + 0x0 + 0x0
+
+ // Call
+ val packet = MedtrumPacket(packetInjector)
+ packet.opCode = opCode.toByte()
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ assertEquals(false, result)
+ assertEquals(true, packet.failed)
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacketTest.kt
new file mode 100644
index 0000000000..5ee0c6ce13
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacketTest.kt
@@ -0,0 +1,91 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import info.nightscout.pump.medtrum.comm.enums.BasalType
+import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
+import info.nightscout.pump.medtrum.extension.toByteArray
+import info.nightscout.rx.events.EventOverviewBolusProgress
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class NotificationPacketTest : MedtrumTestBase() {
+
+ /** Test base behavoir of the Notification packet */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is NotificationPacket) {
+ it.aapsLogger = aapsLogger
+ it.medtrumPump = medtrumPump
+ }
+ }
+ }
+
+ @Test fun handleNotificationGivenStatusAndDataThenStateSaved() {
+ // Inputs
+ val state: Byte = 1
+
+ // Call
+ NotificationPacket(packetInjector).handleNotification(byteArrayOf(state))
+
+ // Expected values
+ assertEquals(medtrumPump.pumpState, MedtrumPumpState.fromByte(state))
+ }
+
+ @Test fun handleNotificationGivenBasalDataThenDataSaved() {
+ // Inputs
+ val data = byteArrayOf(32, 40, 64, 6, 25, 0, 14, 0, 84, -93, -83, 17, 17, 64, 0, -104, 14, 0, 16)
+
+ // Call
+ NotificationPacket(packetInjector).handleNotification(data)
+
+ // Expected values
+ assertEquals(BasalType.ABSOLUTE_TEMP, medtrumPump.lastBasalType)
+ assertEquals(0.85, medtrumPump.lastBasalRate, 0.01)
+ assertEquals(25, medtrumPump.lastBasalSequence)
+ assertEquals(14, medtrumPump.lastBasalPatchId)
+ assertEquals(1685126612000, medtrumPump.lastBasalStartTime)
+ assertEquals(186.80, medtrumPump.reservoir, 0.01)
+ }
+
+ @Test fun handleNotificationGivenSequenceAndOtherDataThenDataSaved() {
+ // Inputs
+ val data = byteArrayOf(32, 0, 17, -89, 0, 14, 0, 0, 0, 0, 0, 0)
+
+ // Call
+ NotificationPacket(packetInjector).handleNotification(data)
+
+ // Expected values
+ assertEquals(167, medtrumPump.currentSequenceNumber)
+ }
+
+ @Test fun handleNotificationGivenBolusInProgressThenDataSaved() {
+ // Inputs
+ val data = byteArrayOf(32, 34, 16, 0, 3, 0, -58, 12, 0, 0, 0, 0, 0)
+ medtrumPump.bolusingTreatment = EventOverviewBolusProgress.Treatment(0.0, 0, false, 1)
+
+ // Call
+ NotificationPacket(packetInjector).handleNotification(data)
+
+ // Expected values
+ assertEquals(false, medtrumPump.bolusDone)
+ assertEquals(0.15, medtrumPump.bolusingTreatment!!.insulin, 0.01)
+ assertEquals(163.5, medtrumPump.reservoir, 0.01)
+ }
+
+ @Test fun handleNotificationGivenBolusFinnishedThenDataSaved() {
+ // Inputs
+ val data = byteArrayOf(32, 34, 17, -128, 33, 0, -89, 12, -80, 0, 14, 0, 0, 0, 0, 0, 0)
+ medtrumPump.bolusingTreatment = EventOverviewBolusProgress.Treatment(0.0, 0, false, 1)
+
+ // Call
+ NotificationPacket(packetInjector).handleNotification(data)
+
+ // Expected values
+ assertEquals(true, medtrumPump.bolusDone)
+ assertEquals(1.65, medtrumPump.bolusingTreatment!!.insulin, 0.01)
+ assertEquals(161.95, medtrumPump.reservoir, 0.01)
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/PollPatchPacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/PollPatchPacketTest.kt
new file mode 100644
index 0000000000..1763c59f10
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/PollPatchPacketTest.kt
@@ -0,0 +1,33 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class PollPatchPacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is MedtrumPacket) {
+ it.aapsLogger = aapsLogger
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val opCode = 30
+
+ // Call
+ val packet = PollPatchPacket(packetInjector)
+ val result = packet.getRequest()
+
+ // Expected values
+ assertEquals(1, result.size)
+ assertEquals(opCode.toByte(), result[0])
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/PrimePacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/PrimePacketTest.kt
new file mode 100644
index 0000000000..b1b941540b
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/PrimePacketTest.kt
@@ -0,0 +1,33 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class PrimePacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is MedtrumPacket) {
+ it.aapsLogger = aapsLogger
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val opCode = 16
+
+ // Call
+ val packet = PrimePacket(packetInjector)
+ val result = packet.getRequest()
+
+ // Expected values
+ assertEquals(1, result.size)
+ assertEquals(opCode.toByte(), result[0])
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/ReadBolusStatePacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/ReadBolusStatePacketTest.kt
new file mode 100644
index 0000000000..c05b79a7be
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/ReadBolusStatePacketTest.kt
@@ -0,0 +1,54 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import info.nightscout.pump.medtrum.extension.toByteArray
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class ReadBolusStatePacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is MedtrumPacket) {
+ it.aapsLogger = aapsLogger
+ }
+ }
+ }
+
+ @Test fun handleResponseGivenPacketWhenValuesSetThenReturnCorrectValues() {
+ // Inputs
+ val opCode = 34
+ val responseCode = 0
+ val bolusData = byteArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+ val response = byteArrayOf(0) + opCode.toByte() + 0.toByteArray(2) + responseCode.toByteArray(2) + bolusData
+
+ // Call
+ val packet = ReadBolusStatePacket(packetInjector)
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ assertTrue(result)
+ assertFalse(packet.failed)
+ assertEquals(bolusData.contentToString(), packet.bolusData.contentToString())
+ }
+
+ @Test fun handleResponseGivenResponseWhenMessageTooShortThenResultFalse() {
+ // Inputs
+ val opCode = 34
+ val responseCode = 0
+ val response = byteArrayOf(0) + opCode.toByte() + 0.toByteArray(2) + responseCode.toByteArray(2)
+
+ // Call
+ val packet = ReadBolusStatePacket(packetInjector)
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ assertEquals(false, result)
+ assertEquals(true, packet.failed)
+ assertEquals(byteArrayOf().contentToString(), packet.bolusData.contentToString())
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/ResumePumpPacket.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/ResumePumpPacket.kt
new file mode 100644
index 0000000000..05ddc499a5
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/ResumePumpPacket.kt
@@ -0,0 +1,33 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class ResumePumpPacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is MedtrumPacket) {
+ it.aapsLogger = aapsLogger
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val opCode = 29
+
+ // Call
+ val packet = ResumePumpPacket(packetInjector)
+ val result = packet.getRequest()
+
+ // Expected values
+ assertEquals(1, result.size)
+ assertEquals(opCode.toByte(), result[0])
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetBasalProfilePacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetBasalProfilePacketTest.kt
new file mode 100644
index 0000000000..f1919fff32
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetBasalProfilePacketTest.kt
@@ -0,0 +1,75 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import info.nightscout.pump.medtrum.comm.enums.BasalType
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class SetBasalProfilePacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is SetBasalProfilePacket) {
+ it.aapsLogger = aapsLogger
+ it.medtrumPump = medtrumPump
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val opCode = 21
+ val basalProfile = byteArrayOf(8, 2, 3, 4, -1, 0, 0, 0, 0)
+
+ // Call
+ val packet = SetBasalProfilePacket(packetInjector, basalProfile)
+ val result = packet.getRequest()
+
+ // Expected values
+ val expected = byteArrayOf(opCode.toByte()) + 1.toByte() + basalProfile
+ assertEquals(expected.contentToString(), result.contentToString())
+ }
+
+ @Test fun handleResponseGivenPacketWhenValuesSetThenReturnCorrectValues() {
+ // Inputs
+ val repsonse = byteArrayOf(18, 21, 16, 0, 0, 0, 1, 22, 0, 3, 0, -110, 0, -32, -18, 88, 17)
+ val basalProfile = byteArrayOf(8, 2, 3, 4, -1, 0, 0, 0, 0)
+
+ // Call
+ val packet = SetBasalProfilePacket(packetInjector, basalProfile)
+ val result = packet.handleResponse(repsonse)
+
+ // Expected values
+ val expectedBasalType = BasalType.STANDARD
+ val expectedBasalRate = 1.1
+ val expectedBasalSequence = 3
+ val expectedStartTime = 1679575392000L
+ val expectedPatchId = 146L
+
+ assertTrue(result)
+ assertEquals(expectedBasalType, medtrumPump.lastBasalType)
+ assertEquals(expectedBasalRate, medtrumPump.lastBasalRate, 0.01)
+ assertEquals(expectedBasalSequence, medtrumPump.lastBasalSequence)
+ assertEquals(expectedStartTime, medtrumPump.lastBasalStartTime)
+ assertEquals(expectedPatchId, medtrumPump.lastBasalPatchId)
+ assertEquals(basalProfile, medtrumPump.actualBasalProfile)
+ }
+
+ @Test fun handleResponseGivenResponseWhenMessageTooShortThenResultFalse() {
+ // Inputs
+ val response = byteArrayOf(18, 21, 16, 0, 0, 0, 1, 22, 0, 3, 0, -110, 0, -32, -18, 88)
+ val basalProfile = byteArrayOf(8, 2, 3, 4, -1, 0, 0, 0, 0)
+
+ // Call
+ val packet = SetBasalProfilePacket(packetInjector, basalProfile)
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ assertFalse(result)
+ assertTrue(packet.failed)
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetBolusMotorPacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetBolusMotorPacketTest.kt
new file mode 100644
index 0000000000..f07e501c37
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetBolusMotorPacketTest.kt
@@ -0,0 +1,34 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class SetBolusMotorPacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is MedtrumPacket) {
+ it.aapsLogger = aapsLogger
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val opCode = 36
+
+ // Call
+ val packet = SetBolusMotorPacket(packetInjector)
+ val result = packet.getRequest()
+
+ // Expected values
+ val expectedByteArray = byteArrayOf(opCode.toByte()) + 0.toByte()
+ assertEquals(2, result.size)
+ assertEquals(expectedByteArray.contentToString(), result.contentToString())
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetBolusPacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetBolusPacketTest.kt
new file mode 100644
index 0000000000..1f5339cf28
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetBolusPacketTest.kt
@@ -0,0 +1,33 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class SetBolusPacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is MedtrumPacket) {
+ it.aapsLogger = aapsLogger
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val insulin = 2.35
+
+ // Call
+ val packet = SetBolusPacket(packetInjector, insulin)
+ val result = packet.getRequest()
+
+ // Expected values
+ val expected = byteArrayOf(19, 1, 47, 0, 0)
+ assertEquals(expected.contentToString(), result.contentToString())
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetPatchPacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetPatchPacketTest.kt
new file mode 100644
index 0000000000..80bdf591fd
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetPatchPacketTest.kt
@@ -0,0 +1,38 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import info.nightscout.pump.medtrum.comm.enums.AlarmSetting
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class SetPatchPacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is SetPatchPacket) {
+ it.aapsLogger = aapsLogger
+ it.medtrumPump = medtrumPump
+ }
+ }
+ }
+
+ @Test fun getRequestGivenValuesWhenCalledThenReturnValidArray() {
+ // Inputs
+ medtrumPump.desiredPatchExpiration = false
+ medtrumPump.desiredAlarmSetting = AlarmSetting.LIGHT_AND_VIBRATE
+ medtrumPump.desiredDailyMaxInsulin = 40
+ medtrumPump.desiredDailyMaxInsulin = 180
+
+ // Call
+ val packet = SetPatchPacket(packetInjector)
+ val result = packet.getRequest()
+
+ // Expected values
+ val expected = byteArrayOf(35, 1, 32, 3, 16, 14, 0, 0, 12, 0, 0, 30)
+ assertEquals(expected.contentToString(), result.contentToString())
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetTempBasalPacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetTempBasalPacketTest.kt
new file mode 100644
index 0000000000..8220cd7b0d
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetTempBasalPacketTest.kt
@@ -0,0 +1,77 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import info.nightscout.pump.medtrum.comm.enums.BasalType
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class SetTempBasalPacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is SetTempBasalPacket) {
+ it.aapsLogger = aapsLogger
+ it.medtrumPump = medtrumPump
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val absoluteRate = 1.25
+ val duration = 60
+
+ // Call
+ val packet = SetTempBasalPacket(packetInjector, absoluteRate, duration)
+ val result = packet.getRequest()
+
+ // Expected values
+ val expected = byteArrayOf(24, 6, 25, 0, 60, 0)
+ assertEquals(expected.contentToString(), result.contentToString())
+ }
+
+ @Test fun handleResponseGivenPacketWhenValuesSetThenReturnCorrectValues() {
+ // Inputs
+ val absoluteRate = 1.25
+ val duration = 60
+
+ val response = byteArrayOf(18, 24, 12, 0, 0, 0, 6, 25, 0, 2, 0, -110, 0, -56, -19, 88, 17, -89, 0)
+
+ // Call
+ val packet = SetTempBasalPacket(packetInjector, absoluteRate, duration)
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ val expectedBasalType = BasalType.ABSOLUTE_TEMP
+ val expectedBasalRate = 1.25
+ val expectedBasalSequence = 2
+ val expectedStartTime = 1679575112000L
+ val expectedPatchId = 146L
+
+ assertTrue(result)
+ assertEquals(expectedBasalType, medtrumPump.lastBasalType)
+ assertEquals(expectedBasalRate, medtrumPump.lastBasalRate, 0.01)
+ assertEquals(expectedBasalSequence, medtrumPump.lastBasalSequence)
+ assertEquals(expectedStartTime, medtrumPump.lastBasalStartTime)
+ assertEquals(expectedPatchId, medtrumPump.lastBasalPatchId)
+ }
+
+ @Test fun handleResponseGivenResponseWhenMessageTooShortThenResultFalse() {
+ // Inputs
+ val absoluteRate = 1.25
+ val duration = 60
+
+ val response = byteArrayOf(18, 24, 12, 0, 0, 0, 6, 25, 0, 2, 0, -110, 0, -56, -19, 88)
+
+ // Call
+ val packet = SetTempBasalPacket(packetInjector, absoluteRate, duration)
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ assertFalse(result)
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetTimePacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetTimePacketTest.kt
new file mode 100644
index 0000000000..e4eed05395
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetTimePacketTest.kt
@@ -0,0 +1,37 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import info.nightscout.pump.medtrum.extension.toByteArray
+import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class SetTimePacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is MedtrumPacket) {
+ it.aapsLogger = aapsLogger
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val opCode = 10
+ val time = MedtrumTimeUtil().getCurrentTimePumpSeconds()
+
+ // Call
+ val packet = SetTimePacket(packetInjector)
+ val result = packet.getRequest()
+
+ // Expected values
+ val expectedByteArray = byteArrayOf(opCode.toByte()) + 2.toByte() + time.toByteArray(4)
+ assertEquals(6, result.size)
+ assertEquals(expectedByteArray.contentToString(), result.contentToString())
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetTimeZonePacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetTimeZonePacketTest.kt
new file mode 100644
index 0000000000..0d5b5fbc6a
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SetTimeZonePacketTest.kt
@@ -0,0 +1,57 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import info.nightscout.pump.medtrum.extension.toByteArray
+import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class SetTimeZonePacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is MedtrumPacket) {
+ it.aapsLogger = aapsLogger
+ }
+ if (it is SetTimeZonePacket) {
+ it.dateUtil = dateUtil
+ it.medtrumPump = medtrumPump
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val opCode = 12
+ val time = MedtrumTimeUtil().getCurrentTimePumpSeconds()
+ val offsetMins = dateUtil.getTimeZoneOffsetMinutes(dateUtil.now())
+
+ // Call
+ val packet = SetTimeZonePacket(packetInjector)
+ val result = packet.getRequest()
+
+ // Expected values
+ val expectedByteArray = byteArrayOf(opCode.toByte()) + offsetMins.toByteArray(2) + time.toByteArray(4)
+ assertEquals(7, result.size)
+ assertEquals(expectedByteArray.contentToString(), result.contentToString())
+ }
+
+ @Test fun handleResponseGivenPacketWhenValuesSetThenReturnCorrectValues() {
+ // Inputs
+ val response = byteArrayOf(7, 12, 4, 0, 0, 0, -78)
+
+ // Call
+ val packet = SetTimeZonePacket(packetInjector)
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ val expectedOffsetMins = 0
+
+ assertTrue(result)
+ assertEquals(expectedOffsetMins, medtrumPump.pumpTimeZoneOffset)
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/StopPatchPacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/StopPatchPacketTest.kt
new file mode 100644
index 0000000000..ec8c78b597
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/StopPatchPacketTest.kt
@@ -0,0 +1,62 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class StopPatchPacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is StopPatchPacket) {
+ it.aapsLogger = aapsLogger
+ it.medtrumPump = medtrumPump
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val opCode = 31
+
+ // Call
+ val packet = StopPatchPacket(packetInjector)
+ val result = packet.getRequest()
+
+ // Expected values
+ assertEquals(1, result.size)
+ assertEquals(opCode.toByte(), result[0])
+ }
+
+ @Test fun handleResponseGivenPacketWhenValuesSetThenReturnCorrectValues() {
+ // Inputs
+ val response = byteArrayOf(11, 31, 10, 0, 0, 0, 23, 0, -110, 0, -5, 0)
+
+ // Call
+ val packet = StopPatchPacket(packetInjector)
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ val expectedPatchId = 146L
+ val expectedStopSequence = 23
+ assertEquals(expectedPatchId, medtrumPump.lastStopPatchId)
+ assertEquals(expectedStopSequence, medtrumPump.lastStopSequence)
+ }
+
+ @Test fun handleResponseGivenResponseWhenMessageTooShortThenResultFalse() {
+ // Inputs
+ val response = byteArrayOf(11, 31, 10, 0, 0, 0, 23, 0, -110)
+
+ // Call
+ val packet = StopPatchPacket(packetInjector)
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ assertFalse(result)
+ assertTrue(packet.failed)
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SubscribePacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SubscribePacketTest.kt
new file mode 100644
index 0000000000..5f50ff2bf3
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SubscribePacketTest.kt
@@ -0,0 +1,35 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import info.nightscout.pump.medtrum.extension.toByteArray
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class SubscribePacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is MedtrumPacket) {
+ it.aapsLogger = aapsLogger
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val opCode = 4
+
+ // Call
+ val packet = SubscribePacket(packetInjector)
+ val result = packet.getRequest()
+
+ // Expected values
+ val expectedByteArray = byteArrayOf(opCode.toByte()) + 4095.toByteArray(2)
+ assertEquals(3, result.size)
+ assertEquals(expectedByteArray.contentToString(), result.contentToString())
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacketTest.kt
new file mode 100644
index 0000000000..181cc525fc
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacketTest.kt
@@ -0,0 +1,102 @@
+package info.nightscout.pump.medtrum.comm.packets
+
+import dagger.android.AndroidInjector
+import dagger.android.HasAndroidInjector
+import info.nightscout.interfaces.pump.PumpSync
+import info.nightscout.pump.medtrum.MedtrumTestBase
+import info.nightscout.pump.medtrum.comm.enums.BasalType
+import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
+import info.nightscout.pump.medtrum.extension.toByteArray
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class SynchronizePacketTest : MedtrumTestBase() {
+
+ /** Test packet specific behavoir */
+
+ private val packetInjector = HasAndroidInjector {
+ AndroidInjector {
+ if (it is SynchronizePacket) {
+ it.aapsLogger = aapsLogger
+ it.medtrumPump = medtrumPump
+ }
+ if (it is NotificationPacket) {
+ it.aapsLogger = aapsLogger
+ it.medtrumPump = medtrumPump
+ }
+ }
+ }
+
+ @Test fun getRequestGivenPacketWhenCalledThenReturnOpCode() {
+ // Inputs
+ val opCode = 3
+
+ // Call
+ val packet = SynchronizePacket(packetInjector)
+ val result = packet.getRequest()
+
+ // Expected values
+ assertEquals(1, result.size)
+ assertEquals(opCode.toByte(), result[0])
+ }
+
+ @Test fun handleResponseGivenResponseWhenMessageIsCorrectLengthThenResultTrue() {
+ // Inputs
+ val opCode = 3
+ val responseCode = 0
+ val state: Byte = 1
+ val dataFieldsPresent = 4046
+ val syncData = byteArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42)
+ val response = byteArrayOf(0) + opCode.toByte() + 0.toByteArray(2) + responseCode.toByteArray(2) + state + dataFieldsPresent.toByteArray(2) + syncData
+
+ // Call
+ val packet = SynchronizePacket(packetInjector)
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ assertEquals(true, result)
+ assertEquals(false, packet.failed)
+ assertEquals(state, packet.medtrumPump.pumpState.state)
+ }
+
+ @Test fun handleResponseGivenResponseWhenMessageTooShortThenResultFalse() {
+ // Inputs
+ val opCode = 3
+ val responseCode = 0
+ val state = 1
+ val dataFieldsPresent = 4046
+ val response = byteArrayOf(0) + opCode.toByte() + 0.toByteArray(2) + responseCode.toByteArray(2) + state.toByteArray(1) + dataFieldsPresent.toByteArray(2)
+
+ // Call
+ val packet = SynchronizePacket(packetInjector)
+ val result = packet.handleResponse(response)
+
+ // Expected values
+ assertEquals(false, result)
+ assertEquals(true, packet.failed)
+ }
+
+ @Test fun handleResponseContainingSyncDataThenDataSaved() {
+ // Inputs
+ val data = byteArrayOf(47, 3, 3, 1, 0, 0, 32, -18, 13, -128, 5, 0, -128, 0, 0, 6, 25, 0, 14, 0, 84, -93, -83, 17, 17, 64, 0, -104, 14, -8, -119, -83, 17, -16, 11, 90, 26, 0, 14, 0, -69, 31, 0, 0, -116, 14, -56)
+
+ // Call
+ val packet = SynchronizePacket(packetInjector)
+ val result = packet.handleResponse(data)
+
+ // Expected values
+ assertEquals(true, result)
+ assertEquals(false, packet.failed)
+ assertEquals(MedtrumPumpState.ACTIVE, packet.medtrumPump.pumpState)
+ assertEquals(BasalType.ABSOLUTE_TEMP, packet.medtrumPump.lastBasalType)
+ assertEquals(0.85, packet.medtrumPump.lastBasalRate, 0.01)
+ assertEquals(25, packet.medtrumPump.lastBasalSequence)
+ assertEquals(14, packet.medtrumPump.lastBasalPatchId)
+ assertEquals(1685126612000, packet.medtrumPump.lastBasalStartTime)
+ assertEquals(8123, packet.medtrumPump.patchAge)
+ assertEquals(186.80, packet.medtrumPump.reservoir, 0.01)
+ assertEquals(1685120120000, packet.medtrumPump.patchStartTime)
+ assertEquals(5.96875, packet.medtrumPump.batteryVoltage_A, 0.01)
+ assertEquals(2.8125, packet.medtrumPump.batteryVoltage_B, 0.01)
+ }
+}
diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/encryption/CryptTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/encryption/CryptTest.kt
new file mode 100644
index 0000000000..213bcdddbe
--- /dev/null
+++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/encryption/CryptTest.kt
@@ -0,0 +1,27 @@
+package info.nightscout.pump.medtrum.encryption
+
+import org.junit.jupiter.api.Test
+import org.junit.Assert.*
+
+class CryptTest {
+
+ @Test
+ fun GivenSNExpectKey() {
+ val crypt = Crypt()
+
+ val input: Long = 2859923929
+ val expect: Long = 3364239851
+ val output: Long = crypt.keyGen(input)
+ assertEquals(expect, output)
+ }
+
+ @Test
+ fun GivenSNExpectReal() {
+ val crypt = Crypt()
+
+ val input: Long = 2859923929
+ val expect: Long = 126009121
+ val output: Long = crypt.simpleDecrypt(input)
+ assertEquals(expect, output)
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index 137cc8c2bd..24a3acf745 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -33,6 +33,7 @@ include ':pump:diaconn'
include ':pump:eopatch'
include ':pump:eopatch-core'
include ':insight'
+include ':pump:medtrum'
include ':pump:medtronic'
include ':pump:omnipod-common'
include ':pump:omnipod-eros'
diff --git a/ui/src/main/java/info/nightscout/ui/dialogs/BolusProgressDialog.kt b/ui/src/main/java/info/nightscout/ui/dialogs/BolusProgressDialog.kt
index c4b6b85a33..7f6e37944b 100644
--- a/ui/src/main/java/info/nightscout/ui/dialogs/BolusProgressDialog.kt
+++ b/ui/src/main/java/info/nightscout/ui/dialogs/BolusProgressDialog.kt
@@ -150,6 +150,8 @@ class BolusProgressDialog : DaggerDialogFragment() {
BolusProgressData.bolusEnded = true
aapsLogger.error("Unhandled exception", e)
}
+ // Reset stop button
+ BolusProgressData.stopPressed = false
helpActivity?.finish()
}