diff --git a/app/build.gradle b/app/build.gradle
index cb0f5ceac0..f6d9d81088 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -159,6 +159,10 @@ android {
}
useLibrary "org.apache.http.legacy"
+
+ dataBinding { //Deleting it causes a binding error
+ enabled = true
+ }
}
allprojects {
@@ -185,6 +189,7 @@ dependencies {
implementation project(':pump:danars')
implementation project(':pump:danar')
implementation project(':pump:diaconn')
+ implementation project(':pump:eopatch')
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 ce1552c575..154a35ff49 100644
--- a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt
@@ -38,6 +38,7 @@ import info.nightscout.androidaps.plugins.general.wear.WearPlugin
import info.nightscout.androidaps.plugins.general.xdripStatusline.StatusLinePlugin
import info.nightscout.plugins.insulin.InsulinOrefFreePeakPlugin
import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin
+import info.nightscout.androidaps.plugins.pump.eopatch.EopatchPumpPlugin
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
@@ -99,6 +100,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
@Inject lateinit var virtualPumpPlugin: VirtualPumpPlugin
@Inject lateinit var wearPlugin: WearPlugin
@Inject lateinit var maintenancePlugin: MaintenancePlugin
+ @Inject lateinit var eopatchPumpPlugin: EopatchPumpPlugin
@Inject lateinit var passwordCheck: PasswordCheck
@Inject lateinit var nsSettingStatus: NSSettingsStatus
@@ -187,6 +189,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
addPreferencesFromResourceIfEnabled(comboPlugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(medtronicPumpPlugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(diaconnG8Plugin, rootKey, config.PUMPDRIVERS)
+ addPreferencesFromResourceIfEnabled(eopatchPumpPlugin, 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 e33bcec641..8433db9be4 100644
--- a/app/src/main/java/info/nightscout/androidaps/di/AppComponent.kt
+++ b/app/src/main/java/info/nightscout/androidaps/di/AppComponent.kt
@@ -19,6 +19,7 @@ import info.nightscout.androidaps.insight.di.InsightModule
import info.nightscout.androidaps.plugin.general.openhumans.di.OpenHumansModule
import info.nightscout.androidaps.plugins.pump.common.di.PumpCommonModule
import info.nightscout.androidaps.plugins.pump.common.di.RileyLinkModule
+import info.nightscout.androidaps.plugins.pump.eopatch.dagger.EopatchModule
import info.nightscout.androidaps.plugins.pump.medtronic.di.MedtronicModule
import info.nightscout.androidaps.plugins.pump.omnipod.dash.di.OmnipodDashModule
import info.nightscout.androidaps.plugins.pump.omnipod.eros.di.OmnipodErosModule
@@ -69,6 +70,7 @@ import javax.inject.Singleton
WorkersModule::class,
DiaconnG8Module::class,
OpenHumansModule::class,
+ EopatchModule::class,
SharedModule::class,
UiModule::class,
InsulinModule::class
diff --git a/app/src/main/java/info/nightscout/androidaps/di/PluginsModule.kt b/app/src/main/java/info/nightscout/androidaps/di/PluginsModule.kt
index cf02ad8bf9..70fbaa5a9e 100644
--- a/app/src/main/java/info/nightscout/androidaps/di/PluginsModule.kt
+++ b/app/src/main/java/info/nightscout/androidaps/di/PluginsModule.kt
@@ -44,6 +44,7 @@ import info.nightscout.plugins.insulin.InsulinOrefUltraRapidActingPlugin
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin
+import info.nightscout.androidaps.plugins.pump.eopatch.EopatchPumpPlugin
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin
import info.nightscout.androidaps.plugins.pump.omnipod.dash.OmnipodDashPumpPlugin
@@ -182,9 +183,15 @@ abstract class PluginsModule {
@Binds
@PumpDriver
@IntoMap
- @IntKey(160)
+ @IntKey(155)
abstract fun bindDiaconnG8Plugin(plugin: DiaconnG8Plugin): PluginBase
+ @Binds
+ @PumpDriver
+ @IntoMap
+ @IntKey(156)
+ abstract fun bindEopatchPumpPlugin(plugin: EopatchPumpPlugin): PluginBase
+
@Binds
@AllConfigs
@IntoMap
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/StatusLightHandler.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/StatusLightHandler.kt
index 37790752ff..45c83ef7cb 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/StatusLightHandler.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/StatusLightHandler.kt
@@ -16,6 +16,7 @@ import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.WarnColors
import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.androidaps.plugins.pump.eopatch.AppConstant
import info.nightscout.shared.sharedPreferences.SP
import javax.inject.Inject
import javax.inject.Singleton
@@ -34,7 +35,15 @@ class StatusLightHandler @Inject constructor(
/**
* applies the extended statusLight subview on the overview fragment
*/
- fun updateStatusLights(careportal_cannula_age: TextView?, careportal_insulin_age: TextView?, careportal_reservoir_level: TextView?, careportal_sensor_age: TextView?, careportal_sensor_battery_level: TextView?, careportal_pb_age: TextView?, careportal_battery_level: TextView?) {
+ fun updateStatusLights(
+ careportal_cannula_age: TextView?,
+ careportal_insulin_age: TextView?,
+ careportal_reservoir_level: TextView?,
+ careportal_sensor_age: TextView?,
+ careportal_sensor_battery_level: TextView?,
+ careportal_pb_age: TextView?,
+ careportal_battery_level: TextView?
+ ) {
val pump = activePlugin.activePump
val bgSource = activePlugin.activeBgSource
handleAge(careportal_cannula_age, TherapyEvent.Type.CANNULA_CHANGE, R.string.key_statuslights_cage_warning, 48.0, R.string.key_statuslights_cage_critical, 72.0)
@@ -46,7 +55,11 @@ class StatusLightHandler @Inject constructor(
val insulinUnit = rh.gs(R.string.insulin_unit_shortname)
if (pump.model() == PumpType.OMNIPOD_EROS || pump.model() == PumpType.OMNIPOD_DASH) {
- handleOmnipodReservoirLevel(careportal_reservoir_level, R.string.key_statuslights_res_critical, 10.0, R.string.key_statuslights_res_warning, 80.0, pump.reservoirLevel, insulinUnit)
+ handlePatchReservoirLevel(careportal_reservoir_level, R.string.key_statuslights_res_critical, 10.0, R.string.key_statuslights_res_warning, 80.0, pump.reservoirLevel, insulinUnit,
+ OmnipodConstants.MAX_RESERVOIR_READING)
+ } else if (pump.model() == PumpType.EOFLOW_EOPATCH2) {
+ handlePatchReservoirLevel(careportal_reservoir_level, R.string.key_statuslights_res_critical, 10.0, R.string.key_statuslights_res_warning, 80.0, pump.reservoirLevel, insulinUnit,
+ AppConstant.MAX_RESERVOIR_READING)
} else {
handleLevel(careportal_reservoir_level, R.string.key_statuslights_res_critical, 10.0, R.string.key_statuslights_res_warning, 80.0, pump.reservoirLevel, insulinUnit)
}
@@ -95,14 +108,16 @@ class StatusLightHandler @Inject constructor(
// Omnipod only reports reservoir level when it's 50 units or less, so we display "50+U" for any value > 50
@Suppress("SameParameterValue")
- private fun handleOmnipodReservoirLevel(view: TextView?, criticalSetting: Int, criticalDefaultValue: Double, warnSetting: Int, warnDefaultValue: Double, level: Double, units: String) {
- if (level >= OmnipodConstants.MAX_RESERVOIR_READING) {
+ private fun handlePatchReservoirLevel(
+ view: TextView?, criticalSetting: Int, criticalDefaultValue: Double, warnSetting: Int,
+ warnDefaultValue: Double, level: Double, units: String, maxReading: Double
+ ) {
+ if (level >= maxReading) {
@Suppress("SetTextI18n")
- view?.text = " 50+$units"
+ view?.text = " ${maxReading.toInt()}+$units"
view?.setTextColor(rh.gac(view.context, R.attr.defaultTextColor))
} else {
handleLevel(view, criticalSetting, criticalDefaultValue, warnSetting, warnDefaultValue, level, units)
}
}
-
}
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 47dd65198e..d01d0e0b58 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -108,6 +108,7 @@
- DanaRv2
- DanaI
- Diaconn G8
+ - Eoflow Eopatch2
- Medtronic 512/712
- Medtronic 515/715
- Medtronic 522/722
diff --git a/build.gradle b/build.gradle
index 10f3341d0f..84bd819423 100644
--- a/build.gradle
+++ b/build.gradle
@@ -38,6 +38,9 @@ buildscript {
androidx_junit_version = '1.1.3'
androidx_rules_version = '1.4.0'
+ rxandroidble_version = '1.12.1'
+ replayshare_version = '2.2.0'
+
wearable_version = '2.9.0'
play_services_wearable_version = '17.1.0'
play_services_location_version = '20.0.0'
diff --git a/core/src/main/java/info/nightscout/androidaps/data/ProfileSealed.kt b/core/src/main/java/info/nightscout/androidaps/data/ProfileSealed.kt
index 6c3e5b29db..6650bcdf1b 100644
--- a/core/src/main/java/info/nightscout/androidaps/data/ProfileSealed.kt
+++ b/core/src/main/java/info/nightscout/androidaps/data/ProfileSealed.kt
@@ -98,6 +98,7 @@ sealed class ProfileSealed(
override fun isValid(from: String, pump: Pump, config: Config, rh: ResourceHelper, rxBus: RxBus, hardLimits: HardLimits, sendNotifications: Boolean): Profile.ValidityCheck {
val validityCheck = Profile.ValidityCheck()
val description = pump.pumpDescription
+
for (basal in basalBlocks) {
val basalAmount = basal.amount * percentage / 100.0
if (!description.is30minBasalRatesCapable) {
@@ -142,6 +143,7 @@ sealed class ProfileSealed(
break
}
}
+
if (!hardLimits.isInRange(dia, hardLimits.minDia(), hardLimits.maxDia())) {
validityCheck.isValid = false
validityCheck.reasons.add(rh.gs(R.string.value_out_of_hard_limits, rh.gs(R.string.profile_dia), dia))
diff --git a/core/src/main/java/info/nightscout/androidaps/interfaces/CommandQueue.kt b/core/src/main/java/info/nightscout/androidaps/interfaces/CommandQueue.kt
index b81f4fa0fe..f1798a1791 100644
--- a/core/src/main/java/info/nightscout/androidaps/interfaces/CommandQueue.kt
+++ b/core/src/main/java/info/nightscout/androidaps/interfaces/CommandQueue.kt
@@ -17,7 +17,7 @@ interface CommandQueue {
fun independentConnect(reason: String, callback: Callback?)
fun bolusInQueue(): Boolean
fun bolus(detailedBolusInfo: DetailedBolusInfo, callback: Callback?): Boolean
- fun cancelAllBoluses(id: Long)
+ fun cancelAllBoluses(id: Long?)
fun stopPump(callback: Callback?)
fun startPump(callback: Callback?)
fun setTBROverNotification(callback: Callback?, enable: Boolean)
diff --git a/core/src/main/java/info/nightscout/androidaps/plugins/common/ManufacturerType.kt b/core/src/main/java/info/nightscout/androidaps/plugins/common/ManufacturerType.kt
index 49eaccf0d7..75f9eab835 100644
--- a/core/src/main/java/info/nightscout/androidaps/plugins/common/ManufacturerType.kt
+++ b/core/src/main/java/info/nightscout/androidaps/plugins/common/ManufacturerType.kt
@@ -10,5 +10,6 @@ enum class ManufacturerType(val description: String) {
Cellnovo("Cellnovo"),
Roche("Roche"),
Ypsomed("Ypsomed"),
- G2e("G2e");
+ G2e("G2e"),
+ Eoflow("Eoflow");
}
\ No newline at end of file
diff --git a/core/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/Notification.kt b/core/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/Notification.kt
index 11843accfb..1bea9d26dd 100644
--- a/core/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/Notification.kt
+++ b/core/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/Notification.kt
@@ -130,6 +130,7 @@ open class Notification {
const val MDT_INVALID_HISTORY_DATA = 76
const val IDENTIFICATION_NOT_SET = 77
const val PERMISSION_BT = 78
+ const val EOELOW_PATCH_ALERTS = 79
const val USER_MESSAGE = 1000
diff --git a/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpCapability.kt b/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpCapability.kt
index 047cdec681..c2555efeac 100644
--- a/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpCapability.kt
+++ b/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpCapability.kt
@@ -25,6 +25,7 @@ enum class PumpCapability {
OmnipodCapabilities(arrayOf(Bolus, TempBasal, BasalProfileSet, BasalRate30min)),
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)),
BasalRate_Duration15minAllowed,
BasalRate_Duration30minAllowed,
BasalRate_Duration15and30minAllowed(arrayOf(BasalRate_Duration15minAllowed, BasalRate_Duration30minAllowed)),
diff --git a/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpType.kt b/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpType.kt
index f02ba913f4..a9d20d9866 100644
--- a/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpType.kt
+++ b/core/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpType.kt
@@ -370,9 +370,25 @@ enum class PumpType {
baseBasalStep = 0.01,
baseBasalSpecialSteps = null,
pumpCapability = PumpCapability.DiaconnCapabilities,
- source = Sources.DiaconnG8,
- useHardwareLink = true
- );
+ source = Sources.DiaconnG8),
+
+ //EOPatch Pump
+ EOFLOW_EOPATCH2(description = "Eoflow Eopatch2",
+ manufacturer = ManufacturerType.Eoflow,
+ model = "Eopatch",
+ bolusSize = 0.05,
+ specialBolusSize = null,
+ extendedBolusSettings = DoseSettings(0.05, 30, 8 * 60, 0.05, 25.0),
+ pumpTempBasalType = PumpTempBasalType.Absolute,
+ tbrSettings = DoseSettings(0.05, 30, 12 * 60, 0.0, 15.0),
+ specialBasalDurations = PumpCapability.BasalRate_Duration30minAllowed,
+ baseBasalMinValue = 0.05,
+ baseBasalMaxValue = 15.0,
+ baseBasalStep = 0.05,
+ baseBasalSpecialSteps = null,
+ pumpCapability = PumpCapability.EopatchCapabilities,
+ isPatchPump = true,
+ source = Sources.EOPatch2);
val description: String
var manufacturer: ManufacturerType? = null
@@ -460,7 +476,8 @@ enum class PumpType {
InterfaceIDs.PumpType.MDI -> MDI
InterfaceIDs.PumpType.USER -> USER
InterfaceIDs.PumpType.DIACONN_G8 -> DIACONN_G8
- InterfaceIDs.PumpType.CACHE -> TODO()
+ InterfaceIDs.PumpType.EOPATCH2 -> EOFLOW_EOPATCH2
+ InterfaceIDs.PumpType.CACHE -> CACHE
}
}
@@ -591,6 +608,7 @@ enum class PumpType {
MDI -> InterfaceIDs.PumpType.MDI
USER -> InterfaceIDs.PumpType.USER
DIACONN_G8 -> InterfaceIDs.PumpType.DIACONN_G8
+ EOFLOW_EOPATCH2 -> InterfaceIDs.PumpType.EOPATCH2
CACHE -> InterfaceIDs.PumpType.CACHE
}
}
diff --git a/core/src/main/java/info/nightscout/androidaps/utils/userEntry/UserEntryMapper.kt b/core/src/main/java/info/nightscout/androidaps/utils/userEntry/UserEntryMapper.kt
index 9641ddfa6b..da402ac9ee 100644
--- a/core/src/main/java/info/nightscout/androidaps/utils/userEntry/UserEntryMapper.kt
+++ b/core/src/main/java/info/nightscout/androidaps/utils/userEntry/UserEntryMapper.kt
@@ -139,6 +139,7 @@ class UserEntryMapper {
Omnipod (UserEntry.Sources.Omnipod),
OmnipodEros (UserEntry.Sources.OmnipodEros),
OmnipodDash (UserEntry.Sources.OmnipodDash),
+ EOPatch2 (UserEntry.Sources.EOPatch2),
MDI (UserEntry.Sources.MDI),
VirtualPump (UserEntry.Sources.VirtualPump),
SMS (UserEntry.Sources.SMS),
diff --git a/core/src/main/java/info/nightscout/androidaps/utils/userEntry/UserEntryPresentationHelper.kt b/core/src/main/java/info/nightscout/androidaps/utils/userEntry/UserEntryPresentationHelper.kt
index 4d968bb875..e1fba7242d 100644
--- a/core/src/main/java/info/nightscout/androidaps/utils/userEntry/UserEntryPresentationHelper.kt
+++ b/core/src/main/java/info/nightscout/androidaps/utils/userEntry/UserEntryPresentationHelper.kt
@@ -95,6 +95,7 @@ class UserEntryPresentationHelper @Inject constructor(
Sources.Omnipod -> R.drawable.ic_patch_pump_outline
Sources.OmnipodEros -> R.drawable.ic_patch_pump_outline
Sources.OmnipodDash -> R.drawable.ic_patch_pump_outline
+ Sources.EOPatch2 -> R.drawable.ic_eopatch2_128
Sources.MDI -> R.drawable.ic_ict
Sources.VirtualPump -> R.drawable.ic_virtual_pump
Sources.SMS -> R.drawable.ic_sms
diff --git a/core/src/main/res/drawable/ic_eopatch2_128.xml b/core/src/main/res/drawable/ic_eopatch2_128.xml
new file mode 100644
index 0000000000..1f3e107e05
--- /dev/null
+++ b/core/src/main/res/drawable/ic_eopatch2_128.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/crowdin.yml b/crowdin.yml
index 1776a78fd2..0be1fc856d 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -43,6 +43,8 @@ files:
translation: /insight/src/main/res/values-%android_code%/exceptions.xml
- source: /automation/src/main/res/values/strings.xml
translation: /automation/src/main/res/values-%android_code%/strings.xml
+ - source: /pump/eopatch/src/main/res/values/strings.xml
+ translation: /pump/eopatch/src/main/res/values-%android_code%/strings.xml
- source: /pump/diaconn/src/main/res/values/strings.xml
translation: /pump/diaconn/src/main/res/values-%android_code%/strings.xml
- source: /pump/pump-common/src/main/res/values/strings.xml
diff --git a/database/src/main/java/info/nightscout/androidaps/database/embedments/InterfaceIDs.kt b/database/src/main/java/info/nightscout/androidaps/database/embedments/InterfaceIDs.kt
index 7306c89d1f..44452c9a44 100644
--- a/database/src/main/java/info/nightscout/androidaps/database/embedments/InterfaceIDs.kt
+++ b/database/src/main/java/info/nightscout/androidaps/database/embedments/InterfaceIDs.kt
@@ -42,6 +42,7 @@ data class InterfaceIDs(
YPSOPUMP,
MDI,
DIACONN_G8,
+ EOPATCH2,
USER,
CACHE;
diff --git a/database/src/main/java/info/nightscout/androidaps/database/entities/UserEntry.kt b/database/src/main/java/info/nightscout/androidaps/database/entities/UserEntry.kt
index 53e012c85e..4dd0942fac 100644
--- a/database/src/main/java/info/nightscout/androidaps/database/entities/UserEntry.kt
+++ b/database/src/main/java/info/nightscout/androidaps/database/entities/UserEntry.kt
@@ -172,6 +172,7 @@ data class UserEntry(
Omnipod, //No entry currently
OmnipodEros,
OmnipodDash, //No entry currently
+ EOPatch2,
MDI,
VirtualPump,
SMS, //From SMS plugin
diff --git a/gradle.properties b/gradle.properties
index 5690db57d4..3f000a7472 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -17,7 +17,7 @@
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
org.gradle.parallel=true
org.gradle.warning.mode=all
-org.gradle.jvmargs=-Xmx2g -XX:+UseParallelGC
+org.gradle.jvmargs=-Xmx3g -XX:+UseParallelGC
android.enableJetifier=false
android.useAndroidX=true
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 999f62acc8..519b260647 100644
--- a/implementation/src/main/java/info/nightscout/implementation/queue/CommandQueueImplementation.kt
+++ b/implementation/src/main/java/info/nightscout/implementation/queue/CommandQueueImplementation.kt
@@ -348,7 +348,7 @@ class CommandQueueImplementation @Inject constructor(
}
@Synchronized
- override fun cancelAllBoluses(id: Long) {
+ override fun cancelAllBoluses(id: Long?) {
if (!isRunning(CommandType.BOLUS)) {
rxBus.send(EventDismissBolusProgressIfRunning(PumpEnactResult(injector).success(true).enacted(false), id))
}
diff --git a/jacoco_project.gradle b/jacoco_project.gradle
index 59201d201e..b542c95258 100644
--- a/jacoco_project.gradle
+++ b/jacoco_project.gradle
@@ -41,7 +41,8 @@ project.afterEvaluate {
'**/R2$*.class',
'**/*Directions$*',
'**/*Directions.*',
- '**/*Binding.*'
+ '**/*Binding.*',
+ '**/BR.class'
]
def jClasses = subprojects.collect { proj ->
diff --git a/pump/eopatch-core/.gitignore b/pump/eopatch-core/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/pump/eopatch-core/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/pump/eopatch-core/build.gradle b/pump/eopatch-core/build.gradle
new file mode 100644
index 0000000000..37621a5a2b
--- /dev/null
+++ b/pump/eopatch-core/build.gradle
@@ -0,0 +1,2 @@
+configurations.create("default")
+artifacts.add("default", file('libs/eopatch_core.aar'))
\ No newline at end of file
diff --git a/pump/eopatch-core/consumer-rules.pro b/pump/eopatch-core/consumer-rules.pro
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/pump/eopatch-core/libs/eopatch_core.aar b/pump/eopatch-core/libs/eopatch_core.aar
new file mode 100644
index 0000000000..7c48b147a2
Binary files /dev/null and b/pump/eopatch-core/libs/eopatch_core.aar differ
diff --git a/pump/eopatch-core/proguard-rules.pro b/pump/eopatch-core/proguard-rules.pro
new file mode 100644
index 0000000000..481bb43481
--- /dev/null
+++ b/pump/eopatch-core/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
\ No newline at end of file
diff --git a/pump/eopatch-core/src/main/AndroidManifest.xml b/pump/eopatch-core/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..a5918e68ab
--- /dev/null
+++ b/pump/eopatch-core/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/pump/eopatch/.gitignore b/pump/eopatch/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/pump/eopatch/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/pump/eopatch/build.gradle b/pump/eopatch/build.gradle
new file mode 100644
index 0000000000..69c02c04f6
--- /dev/null
+++ b/pump/eopatch/build.gradle
@@ -0,0 +1,30 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
+apply plugin: 'kotlin-allopen'
+apply plugin: 'com.hiya.jacoco-android'
+
+apply from: "${project.rootDir}/core/android_dependencies.gradle"
+apply from: "${project.rootDir}/core/android_module_dependencies.gradle"
+apply from: "${project.rootDir}/core/test_dependencies.gradle"
+apply from: "${project.rootDir}/core/jacoco_global.gradle"
+
+android {
+ namespace 'info.nightscout.androidaps.plugins.pump.eopatch'
+ dataBinding {
+ enabled = true
+ }
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation project(':pump:eopatch-core')
+ implementation project(':libraries')
+ implementation project(':shared')
+ implementation project(':database')
+ implementation project(':core')
+
+ //RxAndroidBle
+ implementation "com.polidea.rxandroidble3:rxandroidble:1.16.0"
+ implementation "com.jakewharton.rx3:replaying-share:3.0.0"
+}
\ No newline at end of file
diff --git a/pump/eopatch/proguard-rules.pro b/pump/eopatch/proguard-rules.pro
new file mode 100644
index 0000000000..481bb43481
--- /dev/null
+++ b/pump/eopatch/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
\ No newline at end of file
diff --git a/pump/eopatch/src/main/AndroidManifest.xml b/pump/eopatch/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..b84a88757c
--- /dev/null
+++ b/pump/eopatch/src/main/AndroidManifest.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/AppConstant.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/AppConstant.kt
new file mode 100644
index 0000000000..67e61ba793
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/AppConstant.kt
@@ -0,0 +1,30 @@
+package info.nightscout.androidaps.plugins.pump.eopatch
+
+interface AppConstant {
+ companion object {
+ const val BASAL_MIN_AMOUNT = 0.05f
+
+ const val INSULIN_UNIT_P = 0.05f
+
+ const val INSULIN_UNIT_STEP_U = INSULIN_UNIT_P
+
+ const val OFF = 0
+ const val ON = 1
+
+ const val PUMP_DURATION_MILLI = 4 * 1000L
+
+ const val BASAL_RATE_PER_HOUR_MIN = BASAL_MIN_AMOUNT
+
+ const val SEGMENT_MAX_SIZE_48 = 48
+ const val SEGMENT_COUNT_MAX = SEGMENT_MAX_SIZE_48
+
+ const val BOLUS_ACTIVE_EXTENDED_WAIT = 0x2
+
+ const val BOLUS_UNIT_STEP = INSULIN_UNIT_STEP_U
+
+ const val DAY_START_MINUTE = 0 * 60
+ const val DAY_END_MINUTE = 24 * 60
+ const val INSULIN_DURATION_MIN = 2.0f
+ const val MAX_RESERVOIR_READING = 50.0
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/CommonUtils.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/CommonUtils.kt
new file mode 100644
index 0000000000..27c85df6fd
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/CommonUtils.kt
@@ -0,0 +1,93 @@
+package info.nightscout.androidaps.plugins.pump.eopatch
+
+import io.reactivex.rxjava3.disposables.Disposable
+import java.util.*
+import kotlin.math.abs
+import kotlin.math.min
+
+object CommonUtils {
+ fun dispose(vararg disposable: Disposable?) {
+ for (d in disposable){
+ d?.let {
+ if (!it.isDisposed) {
+ it.dispose()
+ }
+ }
+ }
+ }
+
+ fun hasText(str: CharSequence?): Boolean {
+ if (str == null || str.isEmpty()) {
+ return false
+ }
+ val strLen = str.length
+ for (i in 0 until strLen) {
+ if (!Character.isWhitespace(str[i])) {
+ return true
+ }
+ }
+ return false
+ }
+
+ fun hasText(str: String?): Boolean {
+ return str?.let{hasText(it as CharSequence)}?:false
+ }
+
+ fun isStringEmpty(cs: CharSequence?): Boolean {
+ return cs == null || cs.isEmpty()
+ }
+
+ @JvmStatic fun dateString(millis: Long): String {
+ if(millis == 0L) return ""
+
+ val c = Calendar.getInstance()
+ c.timeInMillis = millis
+ return dateString(c)
+ }
+
+ fun dateString(c: Calendar): String {
+ return String.format(Locale.US, "%04d-%02d-%02d %02d:%02d:%02d",
+ c.get(Calendar.YEAR),
+ c.get(Calendar.MONTH) + 1,
+ c.get(Calendar.DAY_OF_MONTH),
+ c.get(Calendar.HOUR_OF_DAY),
+ c.get(Calendar.MINUTE),
+ c.get(Calendar.SECOND))
+ }
+
+ fun getRemainHourMin(timeMillis: Long): Pair {
+ val diffHours: Long
+ var diffMinutes: Long
+
+ if (timeMillis >= 0) {
+ diffMinutes = abs(timeMillis / (60 * 1000) % 60) + 1
+ if (diffMinutes == 60L) {
+ diffMinutes = 0
+ diffHours = abs(timeMillis / (60 * 60 * 1000)) + 1
+ } else {
+ diffHours = abs(timeMillis / (60 * 60 * 1000))
+ }
+ } else {
+ diffMinutes = abs(timeMillis / (60 * 1000) % 60)
+ diffHours = abs(timeMillis / (60 * 60 * 1000))
+ }
+ return Pair(diffHours, diffMinutes)
+ }
+
+ fun nearlyEqual(a: Float, b: Float, epsilon: Float): Boolean {
+ val absA = abs(a)
+ val absB = abs(b)
+ val diff = abs(a - b)
+ return if (a == b) {
+ true
+ } else if (a == 0f || b == 0f || absA + absB < java.lang.Float.MIN_NORMAL) {
+ diff < epsilon * java.lang.Float.MIN_NORMAL
+ } else {
+ diff / min(absA + absB, Float.MAX_VALUE) < epsilon
+ }
+ }
+
+ fun clone(src: T): T {
+ return GsonHelper.sharedGson().fromJson(GsonHelper.sharedGson().toJson(src), src.javaClass)
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EONotification.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EONotification.kt
new file mode 100644
index 0000000000..f2ab48588e
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EONotification.kt
@@ -0,0 +1,23 @@
+package info.nightscout.androidaps.plugins.pump.eopatch
+
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
+import javax.inject.Inject
+
+class EONotification constructor() : Notification() {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+
+ constructor(id: Int, text: String, level: Int) : this() {
+ this.id = id
+ date = System.currentTimeMillis()
+ this.text = text
+ this.level = level
+ }
+
+ fun action(buttonText: Int, action: Runnable) {
+ this.buttonText = buttonText
+ this.action = action
+ }
+
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EoPatchRxBus.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EoPatchRxBus.kt
new file mode 100644
index 0000000000..abc8625272
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EoPatchRxBus.kt
@@ -0,0 +1,17 @@
+package info.nightscout.androidaps.plugins.pump.eopatch
+
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.subjects.PublishSubject
+
+object EoPatchRxBus {
+ private val publishSubject: PublishSubject = PublishSubject.create()
+
+ fun publish(event: Any) {
+ publishSubject.onNext(event)
+ }
+
+ fun listen(eventType: Class): Observable {
+ return publishSubject.ofType(eventType)
+ }
+
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EopatchPumpPlugin.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EopatchPumpPlugin.kt
new file mode 100644
index 0000000000..91bf8122ca
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EopatchPumpPlugin.kt
@@ -0,0 +1,562 @@
+package info.nightscout.androidaps.plugins.pump.eopatch
+
+import android.os.SystemClock
+import dagger.android.HasAndroidInjector
+import info.nightscout.androidaps.data.DetailedBolusInfo
+import info.nightscout.androidaps.data.PumpEnactResult
+import info.nightscout.androidaps.events.EventAppInitialized
+import info.nightscout.androidaps.events.EventPreferenceChange
+import info.nightscout.androidaps.interfaces.CommandQueue
+import info.nightscout.androidaps.interfaces.PluginDescription
+import info.nightscout.androidaps.interfaces.PluginType
+import info.nightscout.androidaps.interfaces.Profile
+import info.nightscout.androidaps.interfaces.Pump
+import info.nightscout.androidaps.interfaces.PumpDescription
+import info.nightscout.androidaps.interfaces.PumpPluginBase
+import info.nightscout.androidaps.interfaces.PumpSync
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.common.ManufacturerType
+import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction
+import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType
+import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
+import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress
+import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
+import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmManager
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPatchManager
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager
+import info.nightscout.androidaps.plugins.pump.eopatch.code.BolusExDuration
+import info.nightscout.androidaps.plugins.pump.eopatch.code.SettingKeys
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.EopatchOverviewFragment
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasal
+import info.nightscout.androidaps.queue.commands.CustomCommand
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.utils.FabricPrivacy
+import info.nightscout.androidaps.utils.T
+import info.nightscout.androidaps.utils.TimeChangeType
+import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.functions.Consumer
+import io.reactivex.rxjava3.subjects.BehaviorSubject
+import org.json.JSONObject
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlin.math.min
+import kotlin.math.roundToInt
+
+@Singleton
+class EopatchPumpPlugin @Inject constructor(
+ injector: HasAndroidInjector,
+ aapsLogger: AAPSLogger,
+ rh: ResourceHelper,
+ commandQueue: CommandQueue,
+ private val aapsSchedulers: AapsSchedulers,
+ private val rxBus: RxBus,
+ private val fabricPrivacy: FabricPrivacy,
+ private val dateUtil: DateUtil,
+ private val pumpSync: PumpSync,
+ private val patchManager: IPatchManager,
+ private val alarmManager: IAlarmManager,
+ private val preferenceManager: IPreferenceManager
+):PumpPluginBase(PluginDescription()
+ .mainType(PluginType.PUMP)
+ .fragmentClass(EopatchOverviewFragment::class.java.name)
+ .pluginIcon(R.drawable.ic_eopatch2_128)
+ .pluginName(R.string.eopatch)
+ .shortName(R.string.eopatch_shortname)
+ .preferencesId(R.xml.pref_eopatch)
+ .description(R.string.eopatch_pump_description), injector, aapsLogger, rh, commandQueue
+), Pump {
+
+ private val mDisposables = CompositeDisposable()
+
+ private var mPumpType: PumpType = PumpType.EOFLOW_EOPATCH2
+ private var mLastDataTime: Long = 0
+ private val mPumpDescription = PumpDescription(mPumpType)
+
+ override fun onStart() {
+ super.onStart()
+ mDisposables.add(rxBus
+ .toObservable(EventPreferenceChange::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ event: EventPreferenceChange ->
+ if (event.isChanged(rh, SettingKeys.LOW_RESERVOIR_REMINDERS) || event.isChanged(rh, SettingKeys.EXPIRATION_REMINDERS)) {
+ patchManager.changeReminderSetting()
+ } else if (event.isChanged(rh, SettingKeys.BUZZER_REMINDERS)) {
+ patchManager.changeBuzzerSetting()
+ }
+ }) { throwable: Throwable -> fabricPrivacy.logException(throwable) }
+ )
+
+ mDisposables.add(rxBus
+ .toObservable(EventAppInitialized::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ preferenceManager.init()
+ patchManager.init()
+ alarmManager.init()
+ }) { throwable: Throwable -> fabricPrivacy.logException(throwable) }
+ )
+ }
+
+ override fun specialEnableCondition(): Boolean {
+ //BG -> FG, restart patch activation and trigger unhandled alarm
+ if(preferenceManager.isInitDone()) {
+ patchManager.checkActivationProcess()
+ alarmManager.restartAll()
+ }
+ return super.specialEnableCondition()
+ }
+
+ override fun onStop() {
+ super.onStop()
+ aapsLogger.debug(LTag.PUMP, "EOPatchPumpPlugin onStop()")
+ }
+
+ override fun isInitialized(): Boolean {
+ return isConnected() && patchManager.isActivated
+ }
+
+ override fun isSuspended(): Boolean {
+ return patchManager.patchState.isNormalBasalPaused
+ }
+
+ override fun isBusy(): Boolean {
+ return false
+ }
+
+ override fun isConnected(): Boolean {
+ return if(patchManager.isDeactivated) true else patchManager.patchConnectionState.isConnected
+ }
+
+ override fun isConnecting(): Boolean {
+ return patchManager.patchConnectionState.isConnecting
+ }
+
+ override fun isHandshakeInProgress(): Boolean {
+ return false
+ }
+
+ override fun finishHandshaking() {
+ }
+
+ override fun connect(reason: String) {
+ aapsLogger.debug(LTag.PUMP,"EOPatch connect - reason:$reason")
+ mLastDataTime = System.currentTimeMillis()
+ }
+
+ override fun disconnect(reason: String) {
+ aapsLogger.debug(LTag.PUMP,"EOPatch disconnect - reason:$reason")
+ }
+
+ override fun stopConnecting() {
+ }
+
+ override fun getPumpStatus(reason: String) {
+ if (patchManager.isActivated) {
+ if ("SMS" == reason) {
+ aapsLogger.debug("Acknowledged AAPS getPumpStatus request it was requested through an SMS")
+ }else{
+ aapsLogger.debug("Acknowledged AAPS getPumpStatus request")
+ }
+ mDisposables.add(patchManager.updateConnection()
+ .subscribe(Consumer {
+ mLastDataTime = System.currentTimeMillis()
+ })
+ )
+ }
+ }
+
+ override fun setNewBasalProfile(profile: Profile): PumpEnactResult {
+ mLastDataTime = System.currentTimeMillis()
+ if(patchManager.isActivated){
+ if(patchManager.patchState.isTempBasalActive || patchManager.patchState.isBolusActive){
+ return PumpEnactResult(injector)
+ }else{
+ var isSuccess: Boolean? = null
+ val result: BehaviorSubject = BehaviorSubject.create()
+ val disposable = result.hide()
+ .subscribe {
+ isSuccess = it
+ }
+
+ val nb = preferenceManager.getNormalBasalManager().convertProfileToNormalBasal(profile)
+ mDisposables.add(patchManager.startBasal(nb)
+ .observeOn(aapsSchedulers.main)
+ .subscribe({ response ->
+ result.onNext(response.isSuccess)
+ }, {
+ result.onNext(false)
+ })
+ )
+
+ do{
+ SystemClock.sleep(100)
+ }while(isSuccess == null)
+
+ disposable.dispose()
+ aapsLogger.info(LTag.PUMP, "Basal Profile was set: ${isSuccess?:false}")
+ if(isSuccess == true) {
+ rxBus.send(EventNewNotification(Notification(Notification.PROFILE_SET_OK, rh.gs(R.string.profile_set_ok), Notification.INFO, 60)))
+ return PumpEnactResult(injector).success(true).enacted(true)
+ }else{
+ return PumpEnactResult(injector)
+ }
+ }
+ }else{
+ preferenceManager.getNormalBasalManager().setNormalBasal(profile)
+ preferenceManager.flushNormalBasalManager()
+ rxBus.send(EventNewNotification(Notification(Notification.PROFILE_SET_OK, rh.gs(R.string.profile_set_ok), Notification.INFO, 60)))
+ return PumpEnactResult(injector).success(true).enacted(true)
+ }
+ }
+
+ override fun isThisProfileSet(profile: Profile): Boolean {
+ // if (!patchManager.isActivated) {
+ // return true
+ // }
+
+ val ret = preferenceManager.getNormalBasalManager().isEqual(profile)
+ aapsLogger.info(LTag.PUMP, "Is this profile set? $ret")
+ return ret
+ }
+
+ override fun lastDataTime(): Long {
+ return mLastDataTime
+ }
+
+ override val baseBasalRate: Double
+ get() {
+ if (!patchManager.isActivated || patchManager.patchState.isNormalBasalPaused) {
+ return 0.0
+ }
+
+ return preferenceManager.getNormalBasalManager().normalBasal.getCurrentSegment()?.doseUnitPerHour?.toDouble()?:0.05
+ }
+
+ override val reservoirLevel: Double
+ get() {
+ if (!patchManager.isActivated) {
+ return 0.0
+ }
+
+ return patchManager.patchState.remainedInsulin.toDouble()
+ }
+
+ override val batteryLevel: Int
+ get() {
+ return if(patchManager.isActivated) {
+ patchManager.patchState.batteryLevel()
+ }else{
+ 0
+ }
+ }
+
+ override fun deliverTreatment(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult {
+
+ if (detailedBolusInfo.insulin == 0.0 && detailedBolusInfo.carbs == 0.0) {
+ // neither carbs nor bolus requested
+ aapsLogger.error("deliverTreatment: Invalid input: neither carbs nor insulin are set in treatment")
+ return PumpEnactResult(injector).success(false).enacted(false).bolusDelivered(0.0).carbsDelivered(0.0)
+ .comment(rh.gs(R.string.invalidinput))
+ } else if (detailedBolusInfo.insulin > 0.0) {
+ var isSuccess = true
+ val result = BehaviorSubject.createDefault(true)
+ val disposable = result.hide()
+ .subscribe {
+ isSuccess = it
+ }
+
+ mDisposables.add(patchManager.startCalculatorBolus(detailedBolusInfo)
+ .doOnSuccess {
+ mLastDataTime = System.currentTimeMillis()
+ }.subscribe({
+ result.onNext(it.isSuccess)
+ }, {
+ result.onNext(false)
+ })
+ )
+
+ val tr = detailedBolusInfo.let {
+ EventOverviewBolusProgress.Treatment(it.insulin, it.carbs.toInt(), it.bolusType === DetailedBolusInfo.BolusType.SMB, it.id)
+ }
+
+ do{
+ SystemClock.sleep(100)
+ if(patchManager.patchConnectionState.isConnected) {
+ val delivering = patchManager.bolusCurrent.nowBolus.injected
+ rxBus.send(EventOverviewBolusProgress.apply {
+ status = rh.gs(R.string.bolusdelivering, delivering)
+ percent = min((delivering / detailedBolusInfo.insulin * 100).toInt(), 100)
+ t = tr
+ })
+ }
+ }while(!patchManager.bolusCurrent.nowBolus.endTimeSynced && isSuccess)
+
+ rxBus.send(EventOverviewBolusProgress.apply {
+ status = rh.gs(R.string.bolusdelivered, detailedBolusInfo.insulin)
+ percent = 100
+ })
+
+ detailedBolusInfo.insulin = patchManager.bolusCurrent.nowBolus.injected.toDouble()
+ patchManager.addBolusToHistory(detailedBolusInfo)
+
+ disposable.dispose()
+
+ return if(isSuccess)
+ PumpEnactResult(injector).success(true)/*.enacted(true)*/.carbsDelivered(detailedBolusInfo.carbs).bolusDelivered(detailedBolusInfo.insulin)
+ else
+ PumpEnactResult(injector).success(false)/*.enacted(false)*/.carbsDelivered(0.0).bolusDelivered(detailedBolusInfo.insulin)
+
+ } else {
+ // no bolus required, carb only treatment
+ patchManager.addBolusToHistory(detailedBolusInfo)
+
+ return PumpEnactResult(injector).success(true).enacted(true).bolusDelivered(0.0)
+ .carbsDelivered(detailedBolusInfo.carbs).comment(rh.gs(info.nightscout.androidaps.core.R.string.ok))
+ }
+ }
+
+ override fun stopBolusDelivering() {
+ mDisposables.add(patchManager.stopNowBolus()
+ .subscribeOn(aapsSchedulers.io)
+ .observeOn(aapsSchedulers.main)
+ .subscribe { it ->
+ rxBus.send(EventOverviewBolusProgress.apply {
+ status = rh.gs(R.string.bolusdelivered, (it.injectedBolusAmount * 0.05f))
+ })
+ }
+ )
+ }
+
+ override fun setTempBasalAbsolute(absoluteRate: Double, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: PumpSync.TemporaryBasalType): PumpEnactResult {
+ aapsLogger.info(LTag.PUMP, "setTempBasalAbsolute - absoluteRate: ${absoluteRate.toFloat()}, durationInMinutes: ${durationInMinutes.toLong()}, enforceNew: $enforceNew")
+ if(patchManager.patchState.isNormalBasalAct){
+ mLastDataTime = System.currentTimeMillis()
+ val tb = TempBasal.createAbsolute(durationInMinutes.toLong(), absoluteRate.toFloat())
+ return patchManager.startTempBasal(tb)
+ .subscribeOn(aapsSchedulers.io)
+ .observeOn(aapsSchedulers.main)
+ .doOnSuccess {
+ pumpSync.syncTemporaryBasalWithPumpId(
+ timestamp = dateUtil.now(),
+ rate = absoluteRate,
+ duration = T.mins(durationInMinutes.toLong()).msecs(),
+ isAbsolute = true,
+ type = tbrType,
+ pumpId = dateUtil.now(),
+ pumpType = PumpType.EOFLOW_EOPATCH2,
+ pumpSerial = serialNumber()
+ )
+ aapsLogger.info(LTag.PUMP,"setTempBasalAbsolute - tbrCurrent:${readTBR()}")
+ }
+ .map { PumpEnactResult(injector).success(true).enacted(true).duration(durationInMinutes).absolute(absoluteRate).isPercent(false).isTempCancel(false) }
+ .onErrorReturnItem(PumpEnactResult(injector).success(false).enacted(false)
+ .comment("Internal error"))
+ .blockingGet()
+ }else{
+ aapsLogger.info(LTag.PUMP,"setTempBasalAbsolute - normal basal is not active")
+ return PumpEnactResult(injector).success(false).enacted(false)
+ }
+ }
+
+ 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")
+ if(patchManager.patchState.isNormalBasalAct && percent != 0){
+ mLastDataTime = System.currentTimeMillis()
+ val tb = TempBasal.createPercent(durationInMinutes.toLong(), percent)
+ return patchManager.startTempBasal(tb)
+ .subscribeOn(aapsSchedulers.io)
+ .observeOn(aapsSchedulers.main)
+ .doOnSuccess {
+ pumpSync.syncTemporaryBasalWithPumpId(
+ timestamp = dateUtil.now(),
+ rate = percent.toDouble(),
+ duration = T.mins(durationInMinutes.toLong()).msecs(),
+ isAbsolute = false,
+ type = tbrType,
+ pumpId = dateUtil.now(),
+ pumpType = PumpType.EOFLOW_EOPATCH2,
+ pumpSerial = serialNumber()
+ )
+ aapsLogger.info(LTag.PUMP,"setTempBasalPercent - tbrCurrent:${readTBR()}")
+ }
+ .map { PumpEnactResult(injector).success(true).enacted(true).duration(durationInMinutes).percent(percent).isPercent(true).isTempCancel(false) }
+ .onErrorReturnItem(PumpEnactResult(injector).success(false).enacted(false)
+ .comment("Internal error"))
+ .blockingGet()
+ }else{
+ aapsLogger.info(LTag.PUMP,"setTempBasalPercent - normal basal is not active")
+ return PumpEnactResult(injector).success(false).enacted(false)
+ }
+ }
+
+ override fun setExtendedBolus(insulin: Double, durationInMinutes: Int): PumpEnactResult {
+ aapsLogger.info(LTag.PUMP,"setExtendedBolus - insulin: $insulin, durationInMinutes: $durationInMinutes")
+
+ return patchManager.startQuickBolus(0f, insulin.toFloat(), BolusExDuration.ofRaw(durationInMinutes))
+ .doOnSuccess {
+ mLastDataTime = System.currentTimeMillis()
+ pumpSync.syncExtendedBolusWithPumpId(
+ timestamp = dateUtil.now(),
+ amount = insulin,
+ duration = T.mins(durationInMinutes.toLong()).msecs(),
+ isEmulatingTB = false,
+ pumpId = dateUtil.now(),
+ pumpType = PumpType.EOFLOW_EOPATCH2,
+ pumpSerial = serialNumber()
+ )
+ }
+ .map { PumpEnactResult(injector).success(true).enacted(true)}
+ .onErrorReturnItem(PumpEnactResult(injector).success(false).enacted(false).bolusDelivered(0.0)
+ .comment(rh.gs(info.nightscout.androidaps.core.R.string.error)))
+ .blockingGet()
+ }
+
+ override fun cancelTempBasal(enforceNew: Boolean): PumpEnactResult {
+ val tbrCurrent = readTBR()
+
+ if (tbrCurrent == null ) {
+ aapsLogger.debug(LTag.PUMP,"cancelTempBasal - TBR already false.")
+ return PumpEnactResult(injector).success(true).enacted(false)
+ }
+
+ if (!patchManager.patchState.isTempBasalActive) {
+ return if (pumpSync.expectedPumpState().temporaryBasal != null) {
+ PumpEnactResult(injector).success(true).enacted(true).isTempCancel(true)
+ }else
+ PumpEnactResult(injector).success(true).isTempCancel(true)
+ }
+
+ return patchManager.stopTempBasal()
+ .doOnSuccess {
+ mLastDataTime = System.currentTimeMillis()
+ aapsLogger.debug(LTag.PUMP,"cancelTempBasal - $it")
+ pumpSync.syncStopTemporaryBasalWithPumpId(
+ timestamp = dateUtil.now(),
+ endPumpId = dateUtil.now(),
+ pumpType = PumpType.EOFLOW_EOPATCH2,
+ pumpSerial = serialNumber()
+ )
+ }
+ .doOnError{
+ aapsLogger.error(LTag.PUMP,"cancelTempBasal() - $it")
+ }
+ .map { PumpEnactResult(injector).success(true).enacted(true).isTempCancel(true)}
+ .onErrorReturnItem(PumpEnactResult(injector).success(false).enacted(false)
+ .comment(rh.gs(info.nightscout.androidaps.core.R.string.error)))
+ .blockingGet()
+ }
+
+ override fun cancelExtendedBolus(): PumpEnactResult {
+ if(patchManager.patchState.isExtBolusActive){
+ return patchManager.stopExtBolus()
+ .doOnSuccess {
+ aapsLogger.debug(LTag.PUMP,"cancelExtendedBolus - success")
+ mLastDataTime = System.currentTimeMillis()
+ pumpSync.syncStopExtendedBolusWithPumpId(
+ timestamp = dateUtil.now(),
+ endPumpId = dateUtil.now(),
+ pumpType = PumpType.EOFLOW_EOPATCH2,
+ pumpSerial = serialNumber()
+ )
+ }
+ .map { PumpEnactResult(injector).success(true).enacted(true).isTempCancel(true)}
+ .onErrorReturnItem(PumpEnactResult(injector).success(false).enacted(false)
+ .comment(rh.gs(info.nightscout.androidaps.core.R.string.error)))
+ .blockingGet()
+ }else{
+ aapsLogger.debug(LTag.PUMP,"cancelExtendedBolus - nothing stops")
+ return if (pumpSync.expectedPumpState().extendedBolus != null) {
+ pumpSync.syncStopExtendedBolusWithPumpId(
+ timestamp = dateUtil.now(),
+ endPumpId = dateUtil.now(),
+ pumpType = PumpType.EOFLOW_EOPATCH2,
+ pumpSerial = serialNumber()
+ )
+ PumpEnactResult(injector).success(true).enacted(true).isTempCancel(true)
+ }else
+ PumpEnactResult(injector)
+ }
+ }
+
+ override fun getJSONStatus(profile: Profile, profileName: String, version: String): JSONObject {
+ return JSONObject()
+ }
+
+ override fun manufacturer(): ManufacturerType {
+ return ManufacturerType.Eoflow
+ }
+
+ override fun model(): PumpType {
+ return PumpType.EOFLOW_EOPATCH2
+ }
+
+ override fun serialNumber(): String {
+ return patchManager.patchConfig.patchSerialNumber
+ }
+
+ override val pumpDescription: PumpDescription
+ get() = mPumpDescription
+
+ override fun shortStatus(veryShort: Boolean): String {
+ if(patchManager.isActivated) {
+ var ret = ""
+ val activeTemp = pumpSync.expectedPumpState().temporaryBasal
+ if (activeTemp != null)
+ ret += "Temp: ${activeTemp.rate} U/hr"
+
+ val activeExtendedBolus = pumpSync.expectedPumpState().extendedBolus
+ if (activeExtendedBolus != null)
+ ret += "Extended: ${activeExtendedBolus.amount} U\n"
+
+ val reservoirStr = patchManager.patchState.remainedInsulin.let {
+ when {
+ it > 50f -> "50+ U"
+ it < 1f -> "0 U"
+ else -> "${it.roundToInt()} U"
+ }
+ }
+
+ ret += "Reservoir: $reservoirStr"
+ ret += "Battery: ${patchManager.patchState.batteryLevel()}"
+ return ret
+ }else{
+ return "EOPatch is not enabled."
+ }
+ }
+
+ override val isFakingTempsByExtendedBoluses: Boolean = false
+
+ override fun loadTDDs(): PumpEnactResult {
+ return PumpEnactResult(injector)
+ }
+
+ override fun canHandleDST(): Boolean {
+ return false
+ }
+
+ override fun getCustomActions(): List? {
+ return null
+ }
+
+ override fun executeCustomAction(customActionType: CustomActionType) {
+
+ }
+
+ override fun executeCustomCommand(customCommand: CustomCommand): PumpEnactResult? {
+ return null
+ }
+
+
+ override fun timezoneOrDSTChanged(timeChangeType: TimeChangeType) {
+
+ }
+
+ private fun readTBR(): PumpSync.PumpState.TemporaryBasal? {
+ return pumpSync.expectedPumpState().temporaryBasal
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/FloatFormatters.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/FloatFormatters.kt
new file mode 100644
index 0000000000..f90514ab22
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/FloatFormatters.kt
@@ -0,0 +1,34 @@
+package info.nightscout.androidaps.plugins.pump.eopatch
+
+import java.util.*
+import java.util.function.Function
+
+object FloatFormatters {
+ val INSULIN = Function{ value -> String.format(Locale.US, "%.2f", value.toFloat()) }
+ val FAT = Function{ value -> String.format(Locale.US, "%.1f", value.toFloat()) }
+ val DURATION = Function{ value -> String.format(Locale.US, "%.1f", value.toFloat()) }
+
+ fun insulin(value: Float): String {
+ return INSULIN.apply(value)
+ }
+
+ fun insulin(value: Float, suffix: String?): String {
+ return if (CommonUtils.isStringEmpty(suffix)) {
+ INSULIN.apply(value)
+ } else {
+ INSULIN.apply(value) +" "+ suffix!!
+ }
+ }
+
+ fun duration(value: Float): String {
+ return DURATION.apply(value)
+ }
+
+ fun duration(value: Float, suffix: String?): String {
+ return if (CommonUtils.isStringEmpty(suffix)) {
+ DURATION.apply(value)
+ } else {
+ DURATION.apply(value) +" " + suffix!!
+ }
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/GsonHelper.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/GsonHelper.kt
new file mode 100644
index 0000000000..9586dd5b85
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/GsonHelper.kt
@@ -0,0 +1,20 @@
+package info.nightscout.androidaps.plugins.pump.eopatch
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+
+object GsonHelper {
+ private var defaultGson: Gson? = null
+
+ init {
+ defaultGson = GsonBuilder().serializeSpecialFloatingPointValues().create()
+ }
+
+ fun sharedGson(): Gson {
+ if (defaultGson == null) {
+ throw RuntimeException("Not configured gson")
+ }
+
+ return defaultGson!!
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/OsAlarmReceiver.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/OsAlarmReceiver.kt
new file mode 100644
index 0000000000..77ca7204ea
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/OsAlarmReceiver.kt
@@ -0,0 +1,17 @@
+package info.nightscout.androidaps.plugins.pump.eopatch
+
+import android.content.Context
+import android.content.Intent
+import dagger.android.DaggerBroadcastReceiver
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.Companion.fromIntent
+import info.nightscout.androidaps.plugins.pump.eopatch.event.EventEoPatchAlarm
+
+class OsAlarmReceiver : DaggerBroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ super.onReceive(context, intent)
+ fromIntent(intent)?.let { alarmCode ->
+ EoPatchRxBus.publish(EventEoPatchAlarm(HashSet().apply { add(alarmCode) }))
+ }
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/OsAlarmService.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/OsAlarmService.java
new file mode 100644
index 0000000000..a03ea1520b
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/OsAlarmService.java
@@ -0,0 +1,77 @@
+package info.nightscout.androidaps.plugins.pump.eopatch;
+
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+
+import java.util.Objects;
+
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
+
+public class OsAlarmService extends Service {
+
+ public static final int FOREGROUND_NOTIFICATION_ID = 34534554;
+
+ private CompositeDisposable compositeDisposable;
+
+ private boolean foreground = false;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ compositeDisposable = new CompositeDisposable();
+
+ startForeground();
+
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ startForeground();
+
+ String action = null;
+
+ if (action == null) {
+ return Service.START_NOT_STICKY;
+ }
+
+ return START_STICKY;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ ((NotificationManager) Objects.requireNonNull(getSystemService(Context.NOTIFICATION_SERVICE))).cancel(FOREGROUND_NOTIFICATION_ID);
+
+ compositeDisposable.dispose();
+ }
+
+ public synchronized void startForeground() {
+ if (!foreground) {
+ foreground = true;
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ public static void start(Context context) {
+ Intent intent = new Intent(context, OsAlarmService.class);
+
+// context.startForegroundService(intent);
+ }
+
+ public static void notifyNotification(Context context, boolean isNetworkAvailable) {
+ notifyNotification(context);
+ }
+
+ public static void notifyNotification(Context context) {
+// Notification builder = getNotification(context);
+// ((NotificationManager) Objects.requireNonNull(context.getSystemService(Context.NOTIFICATION_SERVICE))).notify(FOREGROUND_NOTIFICATION_ID, builder);
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/RxAction.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/RxAction.kt
new file mode 100644
index 0000000000..5ec9fd146a
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/RxAction.kt
@@ -0,0 +1,57 @@
+@file:Suppress("unused")
+
+package info.nightscout.androidaps.plugins.pump.eopatch
+
+import android.os.SystemClock
+import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.logging.AAPSLogger
+import io.reactivex.rxjava3.core.Scheduler
+import io.reactivex.rxjava3.core.Single
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class RxAction @Inject constructor(
+ private val aapsSchedulers: AapsSchedulers,
+ private val aapsLogger: AAPSLogger
+) {
+ enum class RxVoid {
+ INSTANCE
+ }
+
+ private fun sleep(millis: Long) {
+ if (millis <= 0)
+ return
+ SystemClock.sleep(millis)
+ }
+
+ private fun delay(delayMs: Long): Single<*> {
+ return if (delayMs <= 0) {
+ Single.just(1)
+ } else Single.timer(delayMs, TimeUnit.MILLISECONDS)
+
+ }
+
+ fun single(action: Runnable, delayMs: Long, scheduler: Scheduler): Single<*> {
+ return delay(delayMs)
+ .observeOn(scheduler)
+ .flatMap {
+ Single.fromCallable {
+ action.run()
+ RxVoid.INSTANCE
+ }
+ }
+ }
+
+ @JvmOverloads
+ fun runOnMainThread(action: Runnable, delayMs: Long = 0) {
+ single(action, delayMs, aapsSchedulers.main)
+ .subscribe({
+
+ },
+ { e ->
+ aapsLogger.error("SilentObserver.onError() ignore", e)
+ })
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmCode.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmCode.kt
new file mode 100644
index 0000000000..ed47f06f2f
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmCode.kt
@@ -0,0 +1,119 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.alarm
+
+import android.content.Intent
+import android.net.Uri
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import info.nightscout.androidaps.plugins.pump.eopatch.code.AlarmCategory
+import java.util.*
+import java.util.function.Function
+import java.util.stream.Collectors
+import java.util.stream.Stream
+
+enum class AlarmCode(messageResId: Int) {
+ A002(R.string.string_a002), //"Empty reservoir"
+ A003(R.string.string_a003), //"Patch expired"
+ A004(R.string.string_a004), //"Occlusion"
+ A005(R.string.string_a005), //"Power on self test failure"
+ A007(R.string.string_a007), //"Inappropriate temperature"
+ A016(R.string.string_a016), //"Needle insertion Error"
+ A018(R.string.string_a018), //"Patch battery Error"
+ A019(R.string.string_a019), //"Patch battery Error"
+ A020(R.string.string_a020), //"Patch activation Error"
+ A022(R.string.string_a022), //"Patch Error"
+ A023(R.string.string_a023), //"Patch Error"
+ A034(R.string.string_a034), //"Patch Error"
+ A041(R.string.string_a041), //"Patch Error"
+ A042(R.string.string_a042), //"Patch Error"
+ A043(R.string.string_a043), //"Patch Error"
+ A044(R.string.string_a044), //"Patch Error"
+ A106(R.string.string_a106), //"Patch Error"
+ A107(R.string.string_a107), //"Patch Error"
+ A108(R.string.string_a108), //"Patch Error"
+ A116(R.string.string_a116), //"Patch Error"
+ A117(R.string.string_a117), //"Patch Error"
+ A118(R.string.string_a118), //"Patch Error"
+ B000(R.string.string_b000),
+ B001(R.string.string_b001), //"End of insulin suspend"
+ B003(R.string.string_b003), //"Low reservoir"
+ B005(R.string.string_b005), //"Patch operating life expired"
+ B006(R.string.string_b006), //"Patch will expire soon"
+ B012(R.string.string_b012), //"Incomplete Patch activation"
+ B018(R.string.string_b018); //"Patch battery low"
+
+ val type: Char = name[0]
+ val code: Int = name.substring(1).toInt()
+ val resId: Int = messageResId
+
+ val alarmCategory: AlarmCategory
+ get() = when (type) {
+ TYPE_ALARM -> AlarmCategory.ALARM
+ TYPE_ALERT -> AlarmCategory.ALERT
+ else -> AlarmCategory.NONE
+ }
+
+ val aeCode: Int
+ get() {
+ when (type) {
+ TYPE_ALARM -> return code + 100
+ TYPE_ALERT -> return code
+ }
+ return -1
+ }
+
+ val isPatchOccurrenceAlert: Boolean
+ get() = this == B003 || this == B005 || this == B006 || this == B018
+
+ val isPatchOccurrenceAlarm: Boolean
+ get() = this == A002 || this == A003 || this == A004 || this == A018 || this == A019 || this == A022
+ || this == A023 || this == A034 || this == A041 || this == A042 || this == A043 || this == A044 || this == A106
+ || this == A107 || this == A108 || this == A116 || this == A117 || this == A118
+
+ companion object {
+ const val TYPE_ALARM = 'A'
+ const val TYPE_ALERT = 'B'
+
+ private const val SCHEME = "alarmkey"
+ private const val ALARM_KEY_PATH = "alarmkey"
+ private const val QUERY_CODE = "alarmcode"
+
+ private val NAME_MAP = Stream.of(*values())
+ .collect(Collectors.toMap({ obj: AlarmCode -> obj.name }, Function.identity()))
+
+ fun fromStringToCode(name: String): AlarmCode? {
+ return NAME_MAP[name]
+ }
+
+ fun findByPatchAeCode(aeCode: Int): AlarmCode? {
+ return if (aeCode > 100) {
+ fromStringToCode(String.format(Locale.US, "A%03d", aeCode - 100))
+ } else fromStringToCode(String.format(Locale.US, "B%03d", aeCode))
+ }
+
+ @JvmStatic
+ fun getUri(alarmCode: AlarmCode): Uri {
+ return Uri.Builder()
+ .scheme(SCHEME)
+ .authority("info.nightscout.androidaps")
+ .path(ALARM_KEY_PATH)
+ .appendQueryParameter(QUERY_CODE, alarmCode.name)
+ .build()
+ }
+
+ @JvmStatic
+ fun getAlarmCode(uri: Uri): AlarmCode? {
+ if (SCHEME == uri.scheme && ALARM_KEY_PATH == uri.lastPathSegment) {
+ val code = uri.getQueryParameter(QUERY_CODE)
+ if (code.isNullOrBlank()) {
+ return null
+ }
+ return fromStringToCode(code)
+ }
+ return null
+ }
+
+ @JvmStatic
+ fun fromIntent(intent: Intent): AlarmCode? {
+ return intent.data?.let { getAlarmCode(it) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmManager.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmManager.kt
new file mode 100644
index 0000000000..373189ff1a
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmManager.kt
@@ -0,0 +1,203 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.alarm
+
+import android.content.Context
+import android.content.Intent
+import info.nightscout.androidaps.interfaces.ActivePlugin
+import info.nightscout.androidaps.interfaces.CommandQueue
+import info.nightscout.androidaps.interfaces.PumpSync
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
+import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
+import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
+import info.nightscout.androidaps.plugins.pump.eopatch.EONotification
+import info.nightscout.androidaps.plugins.pump.eopatch.EoPatchRxBus
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.A005
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.A016
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.A020
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.B000
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.B001
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.B012
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPatchManager
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager
+import info.nightscout.androidaps.plugins.pump.eopatch.code.AlarmCategory
+import info.nightscout.androidaps.plugins.pump.eopatch.event.EventEoPatchAlarm
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.AlarmHelperActivity
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.Alarms
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.utils.FabricPrivacy
+import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import info.nightscout.shared.sharedPreferences.SP
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.Disposable
+import java.text.SimpleDateFormat
+import java.util.Locale
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlin.math.max
+
+interface IAlarmManager {
+ fun init()
+ fun restartAll()
+}
+
+@Singleton
+class AlarmManager @Inject constructor() : IAlarmManager {
+ @Inject lateinit var patchManager: IPatchManager
+ @Inject lateinit var activePlugin: ActivePlugin
+ @Inject lateinit var commandQueue: CommandQueue
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var resourceHelper: ResourceHelper
+ @Inject lateinit var rxBus: RxBus
+ @Inject lateinit var fabricPrivacy: FabricPrivacy
+ @Inject lateinit var sp: SP
+ @Inject lateinit var context: Context
+ @Inject lateinit var aapsSchedulers: AapsSchedulers
+
+ @Inject lateinit var pm: IPreferenceManager
+ @Inject lateinit var mAlarmRegistry: IAlarmRegistry
+
+ @Inject lateinit var dateUtil: DateUtil
+ @Inject lateinit var pumpSync: PumpSync
+
+ private lateinit var mAlarmProcess: AlarmProcess
+
+ private var compositeDisposable: CompositeDisposable = CompositeDisposable()
+ private var alarmDisposable: Disposable? = null
+
+ @Inject
+ fun onInit() {
+ mAlarmProcess = AlarmProcess(patchManager, rxBus)
+ }
+
+ override fun init(){
+ alarmDisposable = EoPatchRxBus.listen(EventEoPatchAlarm::class.java)
+ .map { it.alarmCodes }
+ .doOnNext { aapsLogger.info(LTag.PUMP,"EventEoPatchAlarm Received") }
+ .concatMap {
+ Observable.fromArray(it)
+ .observeOn(aapsSchedulers.io)
+ .subscribeOn(aapsSchedulers.main)
+ .doOnNext { alarmCodes ->
+ alarmCodes.forEach { alarmCode ->
+ aapsLogger.info(LTag.PUMP,"alarmCode: ${alarmCode.name}")
+ val valid = isValid(alarmCode)
+ if (valid) {
+ if (alarmCode.alarmCategory == AlarmCategory.ALARM || alarmCode == B012) {
+ showAlarmDialog(alarmCode)
+ } else {
+ showNotification(alarmCode)
+ }
+
+ updateState(alarmCode, AlarmState.FIRED)
+ }else{
+ updateState(alarmCode, AlarmState.HANDLE)
+ }
+ }
+ }
+
+ }
+ .subscribe({}, { throwable: Throwable -> fabricPrivacy.logException(throwable) })
+ }
+
+ override fun restartAll() {
+ val now = System.currentTimeMillis()
+ @Suppress("UNCHECKED_CAST")
+ val occurredAlarm= pm.getAlarms().occurred.clone() as HashMap
+ @Suppress("UNCHECKED_CAST")
+ val registeredAlarm = pm.getAlarms().registered.clone() as HashMap
+ compositeDisposable.clear()
+ if(occurredAlarm.isNotEmpty()){
+ EoPatchRxBus.publish(EventEoPatchAlarm(occurredAlarm.keys))
+ }
+
+ if(registeredAlarm.isNotEmpty()){
+ registeredAlarm.forEach { raEntry ->
+ compositeDisposable.add(
+ mAlarmRegistry.add(raEntry.key, max(OS_REGISTER_GAP, raEntry.value.triggerTimeMilli - now))
+ .subscribe()
+ )
+ }
+ }
+ }
+
+ private fun isValid(code: AlarmCode): Boolean{
+ return when(code){
+ A005, A016, A020, B012 -> {
+ aapsLogger.info(LTag.PUMP,"Is $code valid? ${pm.getPatchConfig().hasMacAddress() && pm.getPatchConfig().lifecycleEvent.isSubStepRunning}")
+ pm.getPatchConfig().hasMacAddress() && pm.getPatchConfig().lifecycleEvent.isSubStepRunning
+ }
+ else -> {
+ aapsLogger.info(LTag.PUMP,"Is $code valid? ${pm.getPatchConfig().isActivated}")
+ pm.getPatchConfig().isActivated
+ }
+ }
+ }
+
+ private fun showAlarmDialog(alarmCode: AlarmCode){
+ val i = Intent(context, AlarmHelperActivity::class.java)
+ i.putExtra("soundid", R.raw.error)
+ i.putExtra("code", alarmCode.name)
+ i.putExtra("status", resourceHelper.gs(alarmCode.resId))
+ i.putExtra("title", resourceHelper.gs(R.string.string_alarm))
+ i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(i)
+ }
+
+ private fun showNotification(alarmCode: AlarmCode, timeOffset: Long = 0L){
+ var alarmMsg = resourceHelper.gs(alarmCode.resId)
+ if(alarmCode == B000){
+ val expireTimeValue = pm.getPatchWakeupTimestamp() + TimeUnit.HOURS.toMillis(84)
+ val expireTimeString = SimpleDateFormat(resourceHelper.gs(R.string.date_format_yyyy_m_d_e_a_hh_mm_comma), Locale.US).format(expireTimeValue)
+ alarmMsg = resourceHelper.gs(alarmCode.resId, expireTimeString)
+ }
+ val notification = EONotification(Notification.EOELOW_PATCH_ALERTS + (alarmCode.aeCode + 10000), alarmMsg, Notification.URGENT)
+
+ notification.action(R.string.confirm) {
+ compositeDisposable.add(
+ Single.just(isValid(alarmCode))
+ .flatMap { isValid ->
+ return@flatMap if(isValid) mAlarmProcess.doAction(context, alarmCode)
+ else Single.just(IAlarmProcess.ALARM_HANDLED)
+ }
+ .subscribe { ret ->
+ if(ret == IAlarmProcess.ALARM_HANDLED){
+ if(alarmCode == B001){
+ pumpSync.syncStopTemporaryBasalWithPumpId(
+ timestamp = dateUtil.now(),
+ endPumpId = dateUtil.now(),
+ pumpType = PumpType.EOFLOW_EOPATCH2,
+ pumpSerial = patchManager.patchConfig.patchSerialNumber
+ )
+ }
+ updateState(alarmCode, AlarmState.HANDLE)
+ }else{
+ rxBus.send(EventNewNotification(notification))
+ }
+ })
+ }
+ notification.soundId = R.raw.error
+ notification.date = pm.getPatchConfig().patchWakeupTimestamp + TimeUnit.SECONDS.toMillis(timeOffset)
+ rxBus.send(EventNewNotification(notification))
+ }
+
+ private fun updateState(alarmCode: AlarmCode, state: AlarmState){
+ when(state){
+ AlarmState.REGISTER -> pm.getAlarms().register(alarmCode, 0)
+ AlarmState.FIRED -> pm.getAlarms().occurred(alarmCode)
+ AlarmState.HANDLE -> pm.getAlarms().handle(alarmCode)
+ }
+ pm.flushAlarms()
+ }
+
+ companion object {
+
+ private const val OS_REGISTER_GAP = 3 * 1000L
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmProcess.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmProcess.kt
new file mode 100644
index 0000000000..95efd7050f
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmProcess.kt
@@ -0,0 +1,124 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.alarm
+
+import android.content.Context
+import android.content.DialogInterface
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.EopatchActivity.Companion.createIntentForCheckConnection
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.EopatchActivity.Companion.createIntentForDiscarded
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.EopatchActivity.Companion.createIntentForCannulaInsertionError
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPatchManager
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.*
+import android.content.Intent
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.EopatchActivity
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BaseResponse
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TemperatureResponse
+import info.nightscout.androidaps.plugins.pump.eopatch.event.EventDialog
+import info.nightscout.androidaps.plugins.pump.eopatch.event.EventProgressDialog
+import info.nightscout.androidaps.plugins.pump.eopatch.extension.takeOne
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs.CommonDialog
+import io.reactivex.rxjava3.core.Single
+import java.lang.Exception
+import java.util.concurrent.Callable
+
+interface IAlarmProcess {
+ fun doAction(context: Context, code: AlarmCode): Single
+
+ companion object {
+ const val ALARM_UNHANDLED = 0
+ const val ALARM_PAUSE = 1
+ const val ALARM_HANDLED = 2
+ }
+}
+
+class AlarmProcess(val patchManager: IPatchManager, val rxBus: RxBus) : IAlarmProcess {
+ override fun doAction(context: Context, code: AlarmCode): Single {
+ return when (code) {
+ B001 -> resumeBasalAction(context)
+ A002, A003, A004, A005, A018, A019,
+ A020, A022, A023, A034, A041, A042,
+ A043, A044, A106, A107, A108, A116,
+ A117, A118 -> patchDeactivationAction(context)
+ A007 -> inappropriateTemperatureAction(context)
+ A016 -> needleInsertionErrorAction(context)
+ B000, B003, B018 -> Single.just(IAlarmProcess.ALARM_HANDLED)
+ B005, B006 -> Single.just(IAlarmProcess.ALARM_HANDLED)
+ B012 -> Single.just(IAlarmProcess.ALARM_HANDLED)
+ }
+ }
+
+ private fun startActivityWithSingleTop(context: Context, intent: Intent) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ context.startActivity(intent)
+ }
+
+ private fun showCommunicationFailedDialog(onConfirmed: Runnable) {
+ val dialog = CommonDialog().apply {
+ title = R.string.patch_communication_failed
+ message = R.string.patch_communication_check_helper_1
+ positiveBtn = R.string.string_communication_check
+ positiveListener = DialogInterface.OnClickListener { _, _ ->
+ onConfirmed.run()
+ dismiss()
+ }
+ }
+
+ rxBus.send(EventDialog(dialog, true))
+ }
+
+ private fun actionWithPatchCheckConnection(context: Context, action: Callable>): Single {
+ return if (patchManager.patchConnectionState.isConnected) {
+ try {
+ action.call()
+ } catch (e: Exception) {
+ Single.just(IAlarmProcess.ALARM_PAUSE)
+ }
+ } else {
+ Single.fromCallable {
+ showCommunicationFailedDialog {
+ startActivityWithSingleTop(context,
+ createIntentForCheckConnection(context, goHomeAfterDiscard = true, forceDiscard = true))
+ }
+ IAlarmProcess.ALARM_PAUSE
+ }
+ }
+ }
+
+ private fun resumeBasalAction(context: Context): Single {
+ return actionWithPatchCheckConnection(context) {
+ patchManager.resumeBasal()
+ .map { obj: BaseResponse -> obj.isSuccess }
+ .flatMap { Single.just(it.takeOne(IAlarmProcess.ALARM_HANDLED, IAlarmProcess.ALARM_UNHANDLED)) }
+ }
+ }
+
+ private fun patchDeactivationAction(context: Context): Single {
+ return actionWithPatchCheckConnection(context) {
+ rxBus.send(EventProgressDialog(true, R.string.string_in_progress))
+ patchManager.deactivate(6000, true)
+ .doFinally {
+ rxBus.send(EventProgressDialog(false, R.string.string_in_progress))
+ startActivityWithSingleTop(context, createIntentForDiscarded(context))
+ }
+ .flatMap { Single.just(IAlarmProcess.ALARM_HANDLED) }
+ }
+ }
+
+ private fun needleInsertionErrorAction(context: Context): Single {
+ return Single.fromCallable {
+ startActivityWithSingleTop(context, createIntentForCannulaInsertionError(context))
+ IAlarmProcess.ALARM_HANDLED
+ }
+ }
+
+ private fun inappropriateTemperatureAction(context: Context): Single {
+ return actionWithPatchCheckConnection(context) {
+ patchManager.temperature
+ .map(TemperatureResponse::getTemperature)
+ .map { temp -> (temp >= EopatchActivity.NORMAL_TEMPERATURE_MIN && temp <= EopatchActivity.NORMAL_TEMPERATURE_MAX) }
+ .filter{ok -> ok}
+ .flatMap { patchManager.resumeBasal().map { it.isSuccess.takeOne(IAlarmProcess.ALARM_HANDLED, IAlarmProcess.ALARM_UNHANDLED) }.toMaybe() }
+ .defaultIfEmpty(IAlarmProcess.ALARM_UNHANDLED)
+ }
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmRegistry.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmRegistry.kt
new file mode 100644
index 0000000000..dcd01a5d0f
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmRegistry.kt
@@ -0,0 +1,164 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.alarm
+
+import android.app.AlarmManager
+import android.app.AlarmManager.AlarmClockInfo
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification
+import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
+import info.nightscout.androidaps.plugins.pump.eopatch.EoPatchRxBus
+import info.nightscout.androidaps.plugins.pump.eopatch.OsAlarmReceiver
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.Companion.getUri
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager
+import info.nightscout.androidaps.plugins.pump.eopatch.code.PatchLifecycle
+import info.nightscout.androidaps.plugins.pump.eopatch.core.code.PatchAeCode
+import info.nightscout.androidaps.plugins.pump.eopatch.event.EventEoPatchAlarm
+import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.logging.AAPSLogger
+import io.reactivex.rxjava3.core.Maybe
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.Disposable
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import javax.inject.Singleton
+
+interface IAlarmRegistry {
+ fun add(alarmCode: AlarmCode, triggerAfter: Long, isFirst: Boolean = false): Maybe
+ fun add(patchAeCodes: Set)
+ fun remove(alarmCode: AlarmCode): Maybe
+}
+
+@Singleton
+class AlarmRegistry @Inject constructor() : IAlarmRegistry {
+ @Inject lateinit var mContext: Context
+ @Inject lateinit var pm: IPreferenceManager
+ @Inject lateinit var rxBus: RxBus
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var aapsSchedulers: AapsSchedulers
+
+ private lateinit var mOsAlarmManager: AlarmManager
+ private var mDisposable: Disposable? = null
+ private var compositeDisposable: CompositeDisposable = CompositeDisposable()
+
+ @Inject fun onInit() {
+ mOsAlarmManager = mContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+ mDisposable = pm.observePatchLifeCycle()
+ .observeOn(aapsSchedulers.main)
+ .subscribe {
+ when(it){
+ PatchLifecycle.REMOVE_NEEDLE_CAP -> {
+ val triggerAfter = pm.getPatchConfig().patchWakeupTimestamp + TimeUnit.HOURS.toMillis(1) - System.currentTimeMillis()
+ compositeDisposable.add(add(AlarmCode.A020, triggerAfter).subscribe())
+ }
+ PatchLifecycle.ACTIVATED -> {
+
+ }
+ PatchLifecycle.SHUTDOWN -> {
+ val sources = ArrayList>()
+ sources.add(Maybe.just(true))
+ pm.getAlarms().occurred.let{ occurredAlarms ->
+ if(occurredAlarms.isNotEmpty()){
+ occurredAlarms.keys.forEach { alarmCode ->
+ sources.add(
+ Maybe.just(alarmCode)
+ .observeOn(aapsSchedulers.main)
+ .doOnSuccess { rxBus.send(EventDismissNotification(Notification.EOELOW_PATCH_ALERTS + (alarmCode.aeCode + 10000))) }
+ )
+ }
+ }
+ }
+ pm.getAlarms().registered.let{ registeredAlarms ->
+ if(registeredAlarms.isNotEmpty()){
+ registeredAlarms.keys.forEach { alarmCode ->
+ sources.add(remove(alarmCode))
+ }
+ }
+ }
+ compositeDisposable.add(Maybe.concat(sources)
+ .subscribe {
+ pm.getAlarms().clear()
+ pm.flushAlarms()
+ }
+ )
+ }
+
+ else -> Unit
+ }
+ }
+ }
+
+ override fun add(alarmCode: AlarmCode, triggerAfter: Long, isFirst: Boolean): Maybe {
+ if(pm.getAlarms().occurred.containsKey(alarmCode)){
+ return Maybe.just(alarmCode)
+ }else {
+ val triggerTimeMilli = System.currentTimeMillis() + triggerAfter
+ pm.getAlarms().register(alarmCode, triggerAfter)
+ pm.flushAlarms()
+ if (triggerAfter <= 0L) {
+ EoPatchRxBus.publish(EventEoPatchAlarm(HashSet().apply { add(alarmCode) }, isFirst))
+ return Maybe.just(alarmCode)
+ }
+ return registerOsAlarm(alarmCode, triggerTimeMilli)
+ }
+ }
+
+ override fun add(patchAeCodes: Set) {
+ compositeDisposable.add(
+ Observable.fromIterable(patchAeCodes)
+ .filter{patchAeCodeItem -> AlarmCode.findByPatchAeCode(patchAeCodeItem.aeValue) != null}
+ .observeOn(aapsSchedulers.main)
+ .filter { aeCodes -> AlarmCode.findByPatchAeCode(aeCodes.aeValue) != null }
+ .flatMapMaybe{aeCodeResponse -> add(AlarmCode.findByPatchAeCode(aeCodeResponse.aeValue)!!, 0L, true)}
+ .subscribe()
+ )
+ }
+
+ private fun registerOsAlarm(alarmCode: AlarmCode, triggerTime: Long): Maybe {
+ return Maybe.fromCallable {
+ cancelOsAlarmInternal(alarmCode)
+ val pendingIntent = createPendingIntent(alarmCode, 0)
+ mOsAlarmManager.setAlarmClock(AlarmClockInfo(triggerTime, pendingIntent), pendingIntent)
+ alarmCode
+ }
+ }
+
+ override fun remove(alarmCode: AlarmCode): Maybe {
+ return if(pm.getAlarms().registered.containsKey(alarmCode)) {
+ cancelOsAlarms(alarmCode)
+ .doOnSuccess {
+ pm.getAlarms().unregister(alarmCode)
+ pm.flushAlarms()
+ }
+ .map { alarmCode }
+ }else{
+ Maybe.just(alarmCode)
+ }
+ }
+
+ private fun cancelOsAlarms(vararg alarmCodes: AlarmCode): Maybe {
+ return Observable.fromArray(*alarmCodes)
+ .map(this::cancelOsAlarmInternal)
+ .reduce(Integer::sum)
+ }
+
+ private fun cancelOsAlarmInternal(alarmCode: AlarmCode): Int {
+ val old = createPendingIntent(alarmCode, PendingIntent.FLAG_NO_CREATE)
+ return if (old != null) {
+ mOsAlarmManager.cancel(old)
+ old.cancel()
+ aapsLogger.debug("[${alarmCode}] OS Alarm canceled.")
+ 1
+ } else {
+ aapsLogger.debug("[${alarmCode}] OS Alarm not canceled, not registered.")
+ 0
+ }
+ }
+
+ private fun createPendingIntent(alarmCode: AlarmCode, flag: Int): PendingIntent? {
+ val intent = Intent(mContext, OsAlarmReceiver::class.java).setData(getUri(alarmCode))
+ return PendingIntent.getBroadcast(mContext, 1, intent, flag)
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmState.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmState.java
new file mode 100644
index 0000000000..4029b27300
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmState.java
@@ -0,0 +1,7 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.alarm;
+
+public enum AlarmState {
+ REGISTER,
+ FIRED,
+ HANDLE
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/bindingadapters/OnSafeClickListener.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/bindingadapters/OnSafeClickListener.kt
new file mode 100644
index 0000000000..c157f6fc2f
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/bindingadapters/OnSafeClickListener.kt
@@ -0,0 +1,26 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.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 {
+ // 중복 클릭 방지 시간 설정
+ private const val MIN_CLICK_INTERVAL: Long = 1000
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/bindingadapters/ViewBindingAdapter.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/bindingadapters/ViewBindingAdapter.kt
new file mode 100644
index 0000000000..bae87baf98
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/bindingadapters/ViewBindingAdapter.kt
@@ -0,0 +1,40 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.bindingadapters
+
+import android.view.View
+import android.widget.TextView
+import androidx.annotation.ColorRes
+import androidx.annotation.StringRes
+import androidx.databinding.BindingAdapter
+import info.nightscout.androidaps.plugins.pump.eopatch.extension.check
+import info.nightscout.androidaps.plugins.pump.eopatch.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))
+}
+
+@BindingAdapter("android:text")
+fun setText(view: TextView, @StringRes resId: Int?) {
+ val text = resId?.let { view.context.getString(it) } ?: ""
+ val oldText = view.text
+ if (text.check(oldText)) {
+ view.text = text
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/IPatchManager.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/IPatchManager.java
new file mode 100644
index 0000000000..1cd9b5eef2
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/IPatchManager.java
@@ -0,0 +1,120 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble;
+
+
+import info.nightscout.androidaps.data.DetailedBolusInfo;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.BleConnectionState;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.PatchSelfTestResult;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.ScanList;
+import info.nightscout.androidaps.plugins.pump.eopatch.code.BolusExDuration;
+import info.nightscout.androidaps.plugins.pump.eopatch.code.DeactivationStatus;
+import info.nightscout.androidaps.plugins.pump.eopatch.code.PatchLifecycle;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BasalScheduleSetResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BaseResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusStopResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.ComboBolusStopResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TempBasalScheduleSetResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TemperatureResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.BolusCurrent;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.NormalBasal;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchConfig;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchLifecycleEvent;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasal;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.Single;
+
+public interface IPatchManager {
+ void init();
+
+ IPreferenceManager getPreferenceManager();
+
+ PatchConfig getPatchConfig();
+
+ boolean isActivated();
+
+ boolean isDeactivated();
+
+ Single extends BaseResponse> resumeBasal();
+
+ Observable observePatchLifeCycle();
+
+ Observable observePatchState();
+
+ BleConnectionState getPatchConnectionState();
+
+ void connect();
+
+ void disconnect();
+
+ PatchState getPatchState();
+
+ void updatePatchState(PatchState state);
+
+ BolusCurrent getBolusCurrent();
+
+ Single deactivate(long timeout, boolean force);
+
+ Observable observePatchConnectionState();
+
+ Observable observeBolusCurrent();
+
+ void setConnection();
+
+ Single stopNowBolus();
+
+ Single stopExtBolus();
+
+ Single stopComboBolus();
+
+ Single extends BolusResponse> startQuickBolus(float nowDoseU, float exDoseU, BolusExDuration exDuration);
+
+ Single extends BolusResponse> startCalculatorBolus(DetailedBolusInfo detailedBolusInfo);
+
+
+ Single infoReminderSet(boolean infoReminder);
+
+ Single setLowReservoir(int doseUnit, int hours);
+
+ Single updateConnection();
+
+ long getPatchExpiredTime();
+
+ Single startBasal(NormalBasal basal);
+
+ void updatePatchLifeCycle(PatchLifecycleEvent event);
+
+ Single startBond(String mac);
+
+ Single getPatchInfo(long timeout);
+
+ Single selfTest(long timeout);
+
+ Observable startPriming(long timeout, long count);
+
+ Single checkNeedleSensing(long timeout);
+
+ Single patchActivation(long timeout);
+
+ Single stopAeBeep(int aeCode);
+
+ Single startTempBasal(TempBasal tempBasal);
+
+ Single extends BaseResponse> pauseBasal(float pauseDurationHour);
+
+ Single scan(long timeout);
+
+ Single stopTempBasal();
+
+ Single getTemperature();
+
+ void addBolusToHistory(DetailedBolusInfo originalDetailedBolusInfo);
+
+ void changeBuzzerSetting();
+
+ void changeReminderSetting();
+
+ void checkActivationProcess();
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManager.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManager.java
new file mode 100644
index 0000000000..5bef9e5a6d
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManager.java
@@ -0,0 +1,443 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble;
+
+import android.content.Context;
+import android.content.Intent;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.data.DetailedBolusInfo;
+import info.nightscout.androidaps.events.EventCustomActionsChanged;
+import info.nightscout.androidaps.events.EventPumpStatusChanged;
+import info.nightscout.androidaps.events.EventRefreshOverview;
+import info.nightscout.androidaps.interfaces.ActivePlugin;
+import info.nightscout.androidaps.interfaces.CommandQueue;
+import info.nightscout.androidaps.interfaces.ProfileFunction;
+import info.nightscout.androidaps.interfaces.PumpSync;
+import info.nightscout.androidaps.interfaces.ResourceHelper;
+import info.nightscout.androidaps.plugins.bus.RxBus;
+import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
+import info.nightscout.androidaps.plugins.pump.eopatch.R;
+import info.nightscout.androidaps.plugins.pump.eopatch.RxAction;
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode;
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmRegistry;
+import info.nightscout.androidaps.plugins.pump.eopatch.code.BolusExDuration;
+import info.nightscout.androidaps.plugins.pump.eopatch.code.DeactivationStatus;
+import info.nightscout.androidaps.plugins.pump.eopatch.code.PatchLifecycle;
+import info.nightscout.androidaps.plugins.pump.eopatch.code.SettingKeys;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BasalScheduleSetResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BaseResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusStopResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.ComboBolusStopResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TempBasalScheduleSetResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TemperatureResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.BleConnectionState;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.IPatchScanner;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.PatchScanner;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.PatchSelfTestResult;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.ScanList;
+import info.nightscout.androidaps.plugins.pump.eopatch.event.EventPatchActivationNotComplete;
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.DialogHelperActivity;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.BolusCurrent;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.NormalBasal;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchConfig;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchLifecycleEvent;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasal;
+import info.nightscout.androidaps.utils.DateUtil;
+import info.nightscout.androidaps.utils.rx.AapsSchedulers;
+import info.nightscout.shared.logging.AAPSLogger;
+import info.nightscout.shared.sharedPreferences.SP;
+import io.reactivex.rxjava3.core.Maybe;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.Single;
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
+import io.reactivex.rxjava3.disposables.Disposable;
+
+@Singleton
+public class PatchManager implements IPatchManager {
+
+ @Inject PatchManagerImpl patchManager;
+ @Inject IPreferenceManager pm;
+ @Inject ProfileFunction profileFunction;
+ @Inject ActivePlugin activePlugin;
+ @Inject CommandQueue commandQueue;
+ @Inject AAPSLogger aapsLogger;
+ @Inject ResourceHelper resourceHelper;
+ @Inject RxBus rxBus;
+ @Inject Context context;
+ @Inject SP sp;
+ @Inject PumpSync pumpSync;
+ @Inject DateUtil dateUtil;
+ @Inject RxAction rxAction;
+ @Inject AapsSchedulers aapsSchedulers;
+ @Inject IAlarmRegistry alarmRegistry;
+
+ private IPatchScanner patchScanner;
+ private final CompositeDisposable mCompositeDisposable = new CompositeDisposable();
+ private Disposable mConnectingDisposable = null;
+
+ @Inject
+ public PatchManager() {}
+
+ @Inject
+ void onInit() {
+ patchScanner = new PatchScanner(context);
+
+ mCompositeDisposable.add(observePatchConnectionState()
+ .subscribe(bleConnectionState -> {
+ switch (bleConnectionState) {
+ case DISCONNECTED:
+ rxBus.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED));
+ rxBus.send(new EventRefreshOverview("Eopatch connection state: " + bleConnectionState.name(), true));
+ rxBus.send(new EventCustomActionsChanged());
+ stopObservingConnection();
+ break;
+
+ case CONNECTED:
+ rxBus.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTED));
+ rxBus.send(new EventRefreshOverview("Eopatch connection state: " + bleConnectionState.name(), true));
+ rxBus.send(new EventCustomActionsChanged());
+ stopObservingConnection();
+ break;
+
+ case CONNECTING:
+ mConnectingDisposable = Observable.interval(0, 1, TimeUnit.SECONDS)
+ .observeOn(aapsSchedulers.getMain())
+ .takeUntil(n -> getPatchConnectionState().isConnected() || n > 10 * 60)
+ .subscribe(n -> rxBus.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTING, n.intValue())));
+ break;
+
+ default:
+ stopObservingConnection();
+ }
+ })
+ );
+ mCompositeDisposable.add(rxBus
+ .toObservable(EventPatchActivationNotComplete.class)
+ .observeOn(aapsSchedulers.getIo())
+ .subscribeOn(aapsSchedulers.getMain())
+ .subscribe(eventPatchActivationNotComplete -> {
+ Intent i = new Intent(context, DialogHelperActivity.class);
+ i.putExtra("title", resourceHelper.gs(R.string.patch_activate_reminder_title));
+ i.putExtra("message", resourceHelper.gs(R.string.patch_activate_reminder_desc));
+ i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(i);
+ })
+ );
+
+ }
+
+ @Override
+ public void init() {
+ setConnection();
+ }
+
+ private void stopObservingConnection(){
+ if(mConnectingDisposable != null) {
+ mConnectingDisposable.dispose();
+ mConnectingDisposable = null;
+ }
+ }
+
+ @Override
+ public IPreferenceManager getPreferenceManager() {
+ return pm;
+ }
+
+ @Override
+ public PatchConfig getPatchConfig() {
+ return pm.getPatchConfig();
+ }
+
+ @Override
+ public Observable observePatchLifeCycle() {
+ return pm.observePatchLifeCycle();
+ }
+
+ @Override
+ public synchronized void updatePatchLifeCycle(PatchLifecycleEvent event) {
+ pm.updatePatchLifeCycle(event);
+ }
+
+ @Override
+ public BleConnectionState getPatchConnectionState() {
+ return patchManager.getPatchConnectionState();
+ }
+
+ @Override
+ public Observable observePatchConnectionState() {
+ return patchManager.observePatchConnectionState();
+ }
+
+ @Override
+ public PatchState getPatchState() {
+ return pm.getPatchState();
+ }
+
+ @Override
+ public void updatePatchState(PatchState state) {
+ pm.getPatchState().update(state);
+ pm.flushPatchState();
+ }
+
+ @Override
+ public Observable observePatchState() {
+ return pm.observePatchState();
+ }
+
+ @Override
+ public long getPatchExpiredTime() {
+ return pm.getPatchConfig().getPatchExpiredTime();
+ }
+
+ @Override
+ public BolusCurrent getBolusCurrent() {
+ return pm.getBolusCurrent();
+ }
+
+ @Override
+ public Observable observeBolusCurrent() {
+ return pm.observeBolusCurrent();
+ }
+
+
+ public void connect() {
+ // Nothing (Auto Connect mode)
+ }
+
+ public void disconnect() {
+ // Nothing (Auto Connect mode)
+ }
+
+ @Override
+ public void setConnection() {
+ if(pm.getPatchConfig().hasMacAddress()){
+ patchManager.updateMacAddress(pm.getPatchConfig().getMacAddress(), false);
+ }
+ }
+
+ public boolean isActivated() {
+ return pm.getPatchConfig().isActivated();
+ }
+
+ public boolean isDeactivated() {
+ return pm.getPatchConfig().isDeactivated();
+ }
+
+ public Single startBond(String mac) {
+ return patchManager.startBond(mac);
+ }
+
+ public Single getPatchInfo(long timeout) {
+ return patchManager.getPatchInfo(timeout);
+ }
+
+ public Single selfTest(long timeout) {
+ return patchManager.selfTest(timeout);
+ }
+
+ public Single getTemperature() {
+ return patchManager.getTemperature();
+ }
+
+ public Observable startPriming(long timeout, long count) {
+ return patchManager.startPriming(timeout, count);
+ }
+
+ public Single checkNeedleSensing(long timeout) {
+ return patchManager.checkNeedleSensing(timeout);
+ }
+
+ public Single patchActivation(long timeout) {
+ return patchManager.patchActivation(timeout)
+ .doOnSuccess(success -> {
+// if (success) {
+// pumpSync.insertTherapyEventIfNewWithTimestamp(
+// getPatchConfig().getPatchWakeupTimestamp(),
+// DetailedBolusInfo.EventType.CANNULA_CHANGE,
+// null,
+// null,
+// PumpType.EOFLOW_EOPATCH2,
+// getPatchConfig().getPatchSerialNumber()
+// );
+// pumpSync.insertTherapyEventIfNewWithTimestamp(
+// getPatchConfig().getPatchWakeupTimestamp(),
+// DetailedBolusInfo.EventType.INSULIN_CHANGE,
+// null,
+// null,
+// PumpType.EOFLOW_EOPATCH2,
+// getPatchConfig().getPatchSerialNumber()
+// );
+// }
+ });
+ }
+
+ public Single startBasal(NormalBasal basal) {
+ return patchManager.startBasal(basal);
+ }
+
+ public Single extends BaseResponse> resumeBasal() {
+ return patchManager.resumeBasal();
+ }
+
+
+ public Single extends BaseResponse> pauseBasal(float pauseDurationHour) {
+ return patchManager.pauseBasal(pauseDurationHour);
+ }
+
+ //==============================================================================================
+ // IPatchManager interface [TEMP BASAL]
+ //==============================================================================================
+
+ public Single startTempBasal(TempBasal tempBasal) {
+ return patchManager.startTempBasal(tempBasal);
+ }
+
+ // 템프베이젤 주입 정지
+ // 템프베이젤이 정지되면 자동으로 노멀베이젤이 활성화된다
+ // 외부에서 호출된다. 즉 명시적으로 tempBasal 정지. 이 때는 normalBasal resume 은 PatchState 보고 처리.
+
+ public Single stopTempBasal() {
+ return patchManager.stopTempBasal();
+ }
+
+
+ public Single extends BolusResponse> startQuickBolus(float nowDoseU, float exDoseU,
+ BolusExDuration exDuration) {
+ return patchManager.startQuickBolus(nowDoseU, exDoseU, exDuration);
+ }
+
+
+ public Single extends BolusResponse> startCalculatorBolus(DetailedBolusInfo detailedBolusInfo) {
+ return patchManager.startCalculatorBolus(detailedBolusInfo);
+ }
+
+
+ public Single stopNowBolus() {
+ return patchManager.stopNowBolus();
+ }
+
+
+ public Single stopExtBolus() {
+ return patchManager.stopExtBolus();
+ }
+
+
+ public Single stopComboBolus(){
+ return patchManager.stopComboBolus();
+ }
+
+ public Single deactivate(long timeout, boolean force) {
+ return patchManager.deactivate(timeout, force);
+ }
+
+ public Single infoReminderSet(boolean infoReminder) {
+ return patchManager.infoReminderSet(infoReminder);
+ }
+
+ public Single setLowReservoir(int doseUnit, int hours) {
+ return patchManager.setLowReservoir(doseUnit, hours);
+ }
+
+ public Single updateConnection() {
+ return patchManager.updateConnection();
+ }
+
+ public Single stopAeBeep(int aeCode) {
+ return patchManager.stopAeBeep(aeCode);
+ }
+
+ @Override
+ public Single scan(long timeout) {
+ patchManager.updateMacAddress("", false);
+ pm.getPatchConfig().setMacAddress("");
+ return patchScanner.scan(timeout);
+ }
+
+ @Override
+ public void addBolusToHistory(DetailedBolusInfo originalDetailedBolusInfo) {
+ DetailedBolusInfo detailedBolusInfo = originalDetailedBolusInfo.copy();
+
+ if(detailedBolusInfo.insulin > 0) {
+ pumpSync.syncBolusWithPumpId(
+ detailedBolusInfo.timestamp,
+ detailedBolusInfo.insulin,
+ detailedBolusInfo.getBolusType(),
+ dateUtil.now(),
+ PumpType.EOFLOW_EOPATCH2,
+ patchManager.pm.getPatchSerial()
+ );
+ }
+ if (detailedBolusInfo.carbs > 0) {
+ pumpSync.syncCarbsWithTimestamp(
+ detailedBolusInfo.getCarbsTimestamp() != null ? detailedBolusInfo.getCarbsTimestamp() : detailedBolusInfo.timestamp,
+ detailedBolusInfo.carbs,
+ null,
+ PumpType.USER,
+ patchManager.pm.getPatchSerial()
+ );
+ }
+ }
+
+ @Override
+ public void changeBuzzerSetting() {
+ boolean buzzer = sp.getBoolean(SettingKeys.Companion.getBUZZER_REMINDERS(), false);
+ if(pm.getPatchConfig().getInfoReminder() != buzzer) {
+ if (isActivated()) {
+ mCompositeDisposable.add(infoReminderSet(buzzer)
+ .observeOn(aapsSchedulers.getMain())
+ .subscribe(patchBooleanResponse -> {
+ pm.getPatchConfig().setInfoReminder(buzzer);
+ pm.flushPatchConfig();
+ }));
+ } else {
+ pm.getPatchConfig().setInfoReminder(buzzer);
+ pm.flushPatchConfig();
+ }
+ }
+ }
+
+ @Override
+ public void changeReminderSetting() {
+ int doseUnit = sp.getInt(SettingKeys.Companion.getLOW_RESERVOIR_REMINDERS(), 0);
+ int hours = sp.getInt(SettingKeys.Companion.getEXPIRATION_REMINDERS(), 0);
+ PatchConfig pc = pm.getPatchConfig();
+ if(pc.getLowReservoirAlertAmount() != doseUnit || pc.getPatchExpireAlertTime() != hours) {
+ if (isActivated()) {
+ mCompositeDisposable.add(setLowReservoir(doseUnit, hours)
+ .observeOn(aapsSchedulers.getMain())
+ .doOnSubscribe(disposable -> {
+ if(pc.getPatchExpireAlertTime() != hours){
+ Maybe.just(AlarmCode.B000)
+ .flatMap(alarmCode -> alarmRegistry.remove(alarmCode))
+ .flatMap(alarmCode -> alarmRegistry.add(alarmCode, (pc.getExpireTimestamp() - System.currentTimeMillis() - TimeUnit.HOURS.toMillis(hours)), false))
+ .subscribe();
+ }
+ })
+ .subscribe(patchBooleanResponse -> {
+ pc.setLowReservoirAlertAmount(doseUnit);
+ pc.setPatchExpireAlertTime(hours);
+ pm.flushPatchConfig();
+ }));
+ } else {
+ pc.setLowReservoirAlertAmount(doseUnit);
+ pc.setPatchExpireAlertTime(hours);
+ pm.flushPatchConfig();
+ }
+ }
+ }
+
+ @Override
+ public void checkActivationProcess(){
+ if(getPatchConfig().getLifecycleEvent().isSubStepRunning()
+ && !pm.getAlarms().isOccurring(AlarmCode.A005)
+ && !pm.getAlarms().isOccurring(AlarmCode.A020)) {
+ rxAction.runOnMainThread(() -> rxBus.send(new EventPatchActivationNotComplete()));
+ }
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManagerImpl.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManagerImpl.java
new file mode 100644
index 0000000000..d83878593c
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManagerImpl.java
@@ -0,0 +1,737 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble;
+
+import static android.content.Intent.ACTION_DATE_CHANGED;
+import static android.content.Intent.ACTION_TIMEZONE_CHANGED;
+import static android.content.Intent.ACTION_TIME_CHANGED;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import androidx.annotation.Nullable;
+
+import java.math.BigInteger;
+import java.security.AlgorithmParameters;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.InvalidParameterSpecException;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+import javax.crypto.KeyAgreement;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.data.DetailedBolusInfo;
+import info.nightscout.androidaps.interfaces.PumpSync;
+import info.nightscout.androidaps.plugins.pump.eopatch.EoPatchRxBus;
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.ActivateTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.DeactivateTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.GetPatchInfoTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.InfoReminderTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.NeedleSensingTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.PauseBasalTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.PrimingTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.ResumeBasalTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.SelfTestTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.SetLowReservoirTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StartBondTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StartCalcBolusTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StartNormalBasalTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StartQuickBolusTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StartTempBasalTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StopComboBolusTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StopExtBolusTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StopNowBolusTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StopTempBasalTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.SyncBasalHistoryTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.TaskBase;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.TaskFunc;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.UpdateConnectionTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.code.BolusExDuration;
+import info.nightscout.androidaps.plugins.pump.eopatch.code.DeactivationStatus;
+import info.nightscout.androidaps.plugins.pump.eopatch.code.SettingKeys;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.Patch;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BuzzerStop;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetTemperature;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.PublicKeySend;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.SequenceGet;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.StopAeBeep;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.code.BolusType;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.noti.AlarmNotification;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.noti.BaseNotification;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.noti.InfoNotification;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BasalScheduleSetResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BaseResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusStopResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.ComboBolusStopResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.KeyResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TempBasalScheduleSetResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TemperatureResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.BleConnectionState;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.IBleDevice;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.PatchSelfTestResult;
+import info.nightscout.androidaps.plugins.pump.eopatch.event.EventEoPatchAlarm;
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.receiver.RxBroadcastReceiver;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.BolusCurrent;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.NormalBasal;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchConfig;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasal;
+import info.nightscout.androidaps.utils.rx.AapsSchedulers;
+import info.nightscout.shared.logging.AAPSLogger;
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.shared.sharedPreferences.SP;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.Scheduler;
+import io.reactivex.rxjava3.core.Single;
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
+import io.reactivex.rxjava3.functions.Consumer;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+
+@Singleton
+public class PatchManagerImpl{
+ @Inject IPreferenceManager pm;
+ @Inject Context context;
+ @Inject SP sp;
+ @Inject AAPSLogger aapsLogger;
+ @Inject AapsSchedulers aapsSchedulers;
+ @Inject PumpSync pumpSync;
+
+ @Inject StartBondTask START_BOND;
+ @Inject GetPatchInfoTask GET_PATCH_INFO;
+ @Inject SelfTestTask SELF_TEST;
+ @Inject PrimingTask START_PRIMING;
+ @Inject NeedleSensingTask START_NEEDLE_CHECK;
+
+ IBleDevice patch;
+
+ private final CompositeDisposable compositeDisposable;
+
+ private static final long DEFAULT_API_TIME_OUT = 10; // SECONDS
+
+ private final BuzzerStop BUZZER_STOP;
+ private final GetTemperature TEMPERATURE_GET;
+ private final StopAeBeep ALARM_ALERT_ERROR_BEEP_STOP;
+ private final PublicKeySend PUBLIC_KEY_SET;
+ private final SequenceGet SEQUENCE_GET;
+
+ @Inject
+ public PatchManagerImpl() {
+ compositeDisposable = new CompositeDisposable();
+
+ BUZZER_STOP = new BuzzerStop();
+ TEMPERATURE_GET = new GetTemperature();
+ ALARM_ALERT_ERROR_BEEP_STOP = new StopAeBeep();
+ PUBLIC_KEY_SET = new PublicKeySend();
+ SEQUENCE_GET = new SequenceGet();
+ }
+
+ @Inject
+ void onInit() {
+ patch = Patch.getInstance();
+ patch.init(context);
+ patch.setSeq(pm.getPatchConfig().getSeq15());
+
+ IntentFilter filter = new IntentFilter(ACTION_TIME_CHANGED);
+ filter.addAction(ACTION_DATE_CHANGED);
+ filter.addAction(ACTION_TIMEZONE_CHANGED);
+
+ Observable dateTimeChanged = RxBroadcastReceiver.Companion.create(context, filter);
+
+ compositeDisposable.add(
+ Observable.combineLatest(patch.observeConnected(), pm.observePatchLifeCycle(),
+ (connected, lifeCycle) -> (connected && lifeCycle.isActivated()))
+ .subscribeOn(aapsSchedulers.getIo())
+ .filter(ok -> ok)
+ .observeOn(aapsSchedulers.getIo())
+ .doOnNext(v -> TaskBase.enqueue(TaskFunc.UPDATE_CONNECTION))
+ .retry()
+ .subscribe());
+
+ compositeDisposable.add(
+ Observable.combineLatest(patch.observeConnected(),
+ pm.observePatchLifeCycle().distinctUntilChanged(),
+ dateTimeChanged.startWith(Observable.just(new Intent())),
+ (connected, lifeCycle, value) -> (connected && lifeCycle.isActivated()))
+ .subscribeOn(aapsSchedulers.getIo())
+ .doOnNext(v -> aapsLogger.debug(LTag.PUMP,"Has the date or time changed? "+v))
+ .filter(ok -> ok)
+ .doOnNext(v -> TaskBase.enqueue(TaskFunc.SET_GLOBAL_TIME))
+ .doOnError(e -> aapsLogger.error(LTag.PUMP, "Failed to set EOPatch time."))
+ .retry()
+ .subscribe());
+
+ compositeDisposable.add(
+ patch.observeConnected()
+ .doOnNext(this::onPatchConnected)
+ .subscribe());
+
+ compositeDisposable.add(
+ pm.getPatchConfig().observe().doOnNext(config -> {
+ byte[] newKey = config.getSharedKey();
+ patch.updateEncryptionParam(newKey);
+ }).subscribe()
+ );
+
+ compositeDisposable.add(
+ EoPatchRxBus.INSTANCE.listen(EventEoPatchAlarm.class)
+ .filter(EventEoPatchAlarm::isFirst)
+ .filter(it -> !pm.getPatchConfig().isDeactivated())
+ .filter(it -> patch.getConnectionState().isConnected())
+ .concatMapIterable(EventEoPatchAlarm::getAlarmCodes)
+ .filter(AlarmCode::isPatchOccurrenceAlert)
+ .flatMap(it -> stopAeBeep(it.getAeCode()).toObservable())
+ .subscribe()
+ );
+
+ compositeDisposable.add(
+ EoPatchRxBus.INSTANCE.listen(EventEoPatchAlarm.class)
+ .filter(EventEoPatchAlarm::isFirst)
+ .filter(it -> !pm.getPatchConfig().isDeactivated())
+ .filter(it -> patch.getConnectionState().isConnected())
+ .concatMapIterable(EventEoPatchAlarm::getAlarmCodes)
+ .filter(AlarmCode::isPatchOccurrenceAlarm)
+ .flatMap(it -> pauseBasalImpl(0.0f, System.currentTimeMillis(), it).toObservable())
+ .subscribe()
+ );
+
+
+ monitorPatchNotification();
+ onConnectedUpdateSequence();
+ }
+
+ private void onPatchConnected(boolean connected) {
+ boolean activated = pm.getPatchConfig().isActivated();
+ boolean useEncryption = pm.getPatchConfig().getSharedKey() != null;
+ int doseUnit = sp.getInt(SettingKeys.Companion.getLOW_RESERVOIR_REMINDERS(), 0);
+ int hours = sp.getInt(SettingKeys.Companion.getEXPIRATION_REMINDERS(), 0);
+ boolean buzzer = sp.getBoolean(SettingKeys.Companion.getBUZZER_REMINDERS(), false);
+ PatchConfig pc = pm.getPatchConfig();
+
+ if (connected && activated && useEncryption) {
+ compositeDisposable.add(
+ SEQUENCE_GET.get()
+ .map(KeyResponse::getSequence)
+ .doOnSuccess(sequence -> {
+ if (sequence >= 0) {
+ saveSequence(sequence);
+ }
+ })
+ .flatMap(integer -> {
+ if(pc.getLowReservoirAlertAmount() != doseUnit || pc.getPatchExpireAlertTime() != hours) {
+ return setLowReservoir(doseUnit, hours)
+ .doOnSuccess(patchBooleanResponse -> {
+ pc.setLowReservoirAlertAmount(doseUnit);
+ pc.setPatchExpireAlertTime(hours);
+ pm.flushPatchConfig();
+ }).map(patchBooleanResponse -> true);
+ }
+ return Single.just(true);
+ })
+ .flatMap(ret -> {
+ if(pc.getInfoReminder() != buzzer) {
+ return infoReminderSet(buzzer)
+ .doOnSuccess(patchBooleanResponse -> {
+ pc.setInfoReminder(buzzer);
+ pm.flushPatchConfig();
+ }).map(patchBooleanResponse -> true);
+ }
+ return Single.just(true);
+ })
+ .subscribe());
+ }
+
+ if(!connected && activated){
+ pm.getPatchConfig().updatetDisconnectedTime();
+ }
+ }
+
+ private void monitorPatchNotification() {
+ compositeDisposable.addAll(
+ patch.observeAlarmNotification()
+ .subscribe(
+ this::onAlarmNotification,
+ throwable -> aapsLogger.error(LTag.PUMP, throwable.getMessage() != null ?
+ throwable.getMessage() : "AlarmNotification observation error")
+ ),
+ patch.observeInfoNotification()
+ .filter(state -> pm.getPatchConfig().isActivated())
+ .subscribe(
+ this::onInfoNotification,
+ throwable -> aapsLogger.error(LTag.PUMP, throwable.getMessage() != null ?
+ throwable.getMessage() : "InfoNotification observation error")
+ )
+ );
+ }
+
+
+ private void onConnectedUpdateSequence() {
+
+ }
+
+ //==============================================================================================
+ // preference database update helper
+ //==============================================================================================
+
+ // synchronized lock
+ private final Object lock = new Object();
+
+ private void updatePatchConfig(Consumer consumer, boolean needSave) throws Throwable {
+ synchronized (lock) {
+ consumer.accept(pm.getPatchConfig());
+ if (needSave) {
+ pm.flushPatchConfig();
+ }
+ }
+ }
+
+ synchronized void updateBasal() {
+
+ NormalBasal normalBasal = pm.getNormalBasalManager().getNormalBasal();
+
+ if(normalBasal.updateNormalBasalIndex()) {
+ pm.flushNormalBasalManager();
+ }
+ }
+
+ public void connect() {
+
+ }
+
+ public void disconnect() {
+ }
+
+ /**
+ * getPatchConnection() 을 사용해야 한다.
+ * 아직 Life Cycle 이 Activated 가 아님.
+ *
+ * Activation Process task #1 Get Patch Information from Patch
+ * Fragment: fragment_patch_connect_new
+ */
+
+ public Single startBond(String mac) {
+ return START_BOND.start(mac);
+ }
+
+ public Single getPatchInfo(long timeout) {
+ return GET_PATCH_INFO.get().timeout(timeout, TimeUnit.MILLISECONDS);
+ }
+
+
+ /**
+ * Activation Process task #2 Check Patch is O.K
+ * Fragment: fragment_patch_connect_new
+ */
+ public Single selfTest(long timeout) {
+ return SELF_TEST.start().timeout(timeout, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Activation Process task #3 PRIMING
+ * Fragment: fragment_patch_priming
+ */
+
+ public Single getTemperature() {
+ return TEMPERATURE_GET.get()
+ .timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ public Observable startPriming(long timeout, long count) {
+ return START_PRIMING.start(count)
+ .timeout(timeout, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Activation Process task #4 NEEDLE SENSING
+ * Fragment: fragment_patch_rotate_knob
+ */
+ public Single checkNeedleSensing(long timeout) {
+ return START_NEEDLE_CHECK.start()
+ .timeout(timeout, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Activation Process task #5 Activation Secure Key, Basal writing
+ * Fragment: fragment_patch_check_patch
+ */
+ @Inject
+ ActivateTask ACTIVATE;
+
+ public Single patchActivation(long timeout) {
+
+ return ACTIVATE.start().timeout(timeout, TimeUnit.MILLISECONDS)
+ .flatMap(success -> sharedKey())
+ .flatMap(success -> getSequence())
+ .doOnSuccess(success -> {
+ if (success) {
+ TaskBase.enqueue(TaskFunc.LOW_RESERVOIR);
+ TaskBase.enqueue(TaskFunc.INFO_REMINDER);
+ pumpSync.connectNewPump(true);
+ }
+ });
+ }
+
+
+ //==============================================================================================
+ // IPatchManager interface [NORMAL BASAL]
+ //==============================================================================================
+
+ @Inject
+ StartNormalBasalTask startNormalBasalTask;
+
+ public Single startBasal(NormalBasal basal) {
+
+ return startNormalBasalTask.start(basal)
+ .timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ @Inject
+ ResumeBasalTask resumeBasalTask;
+
+ public Single extends BaseResponse> resumeBasal() {
+ return resumeBasalTask.resume()
+ .timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ public Single extends BaseResponse> pauseBasal(float pauseDurationHour) {
+ return pauseBasalImpl(pauseDurationHour, 0, null)
+ .observeOn(SS)
+ .timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ //==============================================================================================
+ // IPatchManager implementation [NORMAL BASAL]
+ //==============================================================================================
+
+ @Inject
+ PauseBasalTask pauseBasalTask;
+
+ private Single extends BaseResponse> pauseBasalImpl(float pauseDurationHour, long alarmOccurredTime, @Nullable AlarmCode alarmCode) {
+ return pauseBasalTask.pause(pauseDurationHour, alarmOccurredTime, alarmCode);
+ }
+
+ //==============================================================================================
+ // IPatchManager interface [TEMP BASAL]
+ //==============================================================================================
+
+ @Inject
+ StartTempBasalTask startTempBasalTask;
+
+ public Single startTempBasal(TempBasal tempBasal) {
+ return startTempBasalTask.start(tempBasal).timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ // 템프베이젤 주입 정지
+ // 템프베이젤이 정지되면 자동으로 노멀베이젤이 활성화된다
+ // 외부에서 호출된다. 즉 명시적으로 tempBasal 정지. 이 때는 normalBasal resume 은 PatchState 보고 처리.
+
+ @Inject
+ StopTempBasalTask stopTempBasalTask;
+
+ public Single stopTempBasal() {
+ return stopTempBasalTask.stop().timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ @Inject
+ StartQuickBolusTask startQuickBolusTask;
+
+ @Inject
+ StartCalcBolusTask startCalcBolusTask;
+
+ @Inject
+ StopComboBolusTask stopComboBolusTask;
+
+ @Inject
+ StopNowBolusTask stopNowBolusTask;
+
+ @Inject
+ StopExtBolusTask stopExtBolusTask;
+
+
+ public Single extends BolusResponse> startQuickBolus(float nowDoseU, float exDoseU,
+ BolusExDuration exDuration) {
+ return startQuickBolusTask.start(nowDoseU, exDoseU, exDuration)
+ .timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ public Single extends BolusResponse> startCalculatorBolus(DetailedBolusInfo detailedBolusInfo) {
+ return startCalcBolusTask.start(detailedBolusInfo)
+ .timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ public Single stopNowBolus() {
+ return stopNowBolusTask.stop().timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ public Single stopExtBolus() {
+ return stopExtBolusTask.stop().timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ public Single stopComboBolus(){
+ return stopComboBolusTask.stop().timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+// private Single extends BaseResponse> stopNowAndExtBolus() {
+//
+// boolean nowActive = pm.getPatchState().isNowBolusActive();
+// boolean extActive = pm.getPatchState().isExtBolusActive();
+//
+// if (nowActive && extActive) {
+// return stopComboBolus();
+// } else if (nowActive) {
+// return stopNowBolus();
+// } else if (extActive) {
+// return stopExtBolus();
+// }
+//
+// return Single.just(new PatchBooleanResponse(true));
+// }
+
+ //==============================================================================================
+ // IPatchManager implementation [BOLUS]
+ //==============================================================================================
+
+ public void readBolusStatusFromNotification(InfoNotification infoNotification) {
+ if (infoNotification.isBolusRegAct()) {
+ BolusCurrent bolusCurrent = pm.getBolusCurrent();
+
+ Arrays.asList(BolusType.NOW, BolusType.EXT).forEach(type -> {
+ if (infoNotification.isBolusRegAct(type)) { // 완료되었어도 업데이트 필요.
+ int injectedPumpCount = infoNotification.getInjected(type);
+ int remainPumpCount = infoNotification.getRemain(type);
+ bolusCurrent.updateBolusFromPatch(type, injectedPumpCount, remainPumpCount);
+ }
+ });
+ pm.flushBolusCurrent();
+ }
+ }
+
+
+ @Inject
+ DeactivateTask deactivateTask;
+ // Patch Activation Tasks
+ public Single deactivate(long timeout, boolean force) {
+ return deactivateTask.run(force, timeout);
+ }
+
+ @Inject
+ InfoReminderTask infoReminderTask;
+
+ public Single infoReminderSet(boolean infoReminder) {
+ return infoReminderTask.set(infoReminder).timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ @Inject
+ SetLowReservoirTask setLowReservoirTask;
+ public Single setLowReservoir(int doseUnit, int hours) {
+ return setLowReservoirTask.set(doseUnit, hours).timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ @Inject
+ UpdateConnectionTask updateConnectionTask;
+ public Single updateConnection() {
+ return updateConnectionTask.update();
+ }
+
+ public Single stopAeBeep(int aeCode) {
+ return ALARM_ALERT_ERROR_BEEP_STOP.stop(aeCode);
+ }
+
+ synchronized void fetchPatchState() {
+ updateConnectionTask.enqueue();
+ }
+
+ @Inject
+ PatchStateManager patchStateManager;
+
+ @Inject
+ SyncBasalHistoryTask syncBasalHistoryTask;
+
+ void onAlarmNotification(AlarmNotification notification) throws Throwable {
+ patchStateManager.updatePatchState(PatchState.create(notification.patchState, System.currentTimeMillis()));
+
+ if (pm.getPatchConfig().isActivated()) {
+ if(!patch.isSeqReady()){
+ getSequence().subscribe();
+ }
+ updateBasal();
+ updateInjected(notification, true);
+ fetchPatchState();
+ }
+ }
+
+ private void onInfoNotification(InfoNotification notification) throws Throwable {
+ readBolusStatusFromNotification(notification);
+ updateInjected(notification, false);
+ if (notification.isBolusDone()) {
+ fetchPatchState();
+ }
+ }
+
+ void updateInjected(BaseNotification notification, boolean needSave) throws Throwable {
+ updatePatchConfig(patchConfig -> {
+ patchConfig.setInjectCount(notification.getTotalInjected());
+ patchConfig.setStandardBolusInjectCount(notification.getSB_CNT());
+ patchConfig.setExtendedBolusInjectCount(notification.getEB_CNT());
+ patchConfig.setBasalInjectCount(notification.getBasal_CNT());
+ }, needSave);
+ }
+
+ //==============================================================================================
+ // Security
+ //==============================================================================================
+ private static final String SECP256R1 = "secp256r1";
+ private static final String EC = "EC";
+ private static final String ECDH = "ECDH";
+
+ public Single sharedKey() {
+ return genKeyPair().flatMap(keyPair -> ECPublicToRawBytes(keyPair)
+ .flatMap(bytes -> PUBLIC_KEY_SET.send(bytes)
+ .map(KeyResponse::getPublicKey)
+ .map(bytes2 -> rawToEncodedECPublicKey(SECP256R1, bytes2))
+ .map(publicKey -> generateSharedSecret(keyPair.getPrivate(), publicKey))
+ .doOnSuccess(this::saveShared).map(v2 -> true)))
+ .doOnError(e -> aapsLogger.error(LTag.PUMP, "sharedKey error"));
+ }
+
+ public Single getSequence() {
+ return SEQUENCE_GET.get()
+ .map(KeyResponse::getSequence)
+ .doOnSuccess(sequence -> {
+ if (sequence >= 0) {
+ saveSequence(sequence);
+ }
+ })
+ .flatMap(v -> Single.just(true));
+ }
+
+ private void saveShared(byte[] v) {
+ pm.getPatchConfig().setSharedKey(v);
+ pm.flushPatchConfig();
+ }
+
+ private void saveSequence(int sequence) {
+ patch.setSeq(sequence);
+ pm.getPatchConfig().setSeq15(sequence);
+ pm.flushPatchConfig();
+ }
+
+ public Single genKeyPair() {
+ return Single.fromCallable(() -> {
+ ECGenParameterSpec ecSpec_named = new ECGenParameterSpec(SECP256R1);
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance(EC);
+ kpg.initialize(ecSpec_named);
+ return kpg.generateKeyPair();
+ });
+ }
+
+ public Single ECPublicToRawBytes(KeyPair keyPair) {
+ return Single.just(keyPair.getPublic()).cast(ECPublicKey.class)
+ .map(PatchManagerImpl::encodeECPublicKey);
+ }
+
+ private static byte[] encodeECPublicKey(ECPublicKey pubKey) {
+ int keyLengthBytes = pubKey.getParams().getOrder().bitLength()
+ / Byte.SIZE;
+ byte[] publicKeyEncoded = new byte[2 * keyLengthBytes];
+
+ int offset = 0;
+
+ BigInteger x = pubKey.getW().getAffineX();
+ byte[] xba = x.toByteArray();
+ if (xba.length > keyLengthBytes + 1 || xba.length == keyLengthBytes + 1
+ && xba[0] != 0) {
+ throw new IllegalStateException(
+ "X coordinate of EC public key has wrong size");
+ }
+
+ if (xba.length == keyLengthBytes + 1) {
+ System.arraycopy(xba, 1, publicKeyEncoded, offset, keyLengthBytes);
+ } else {
+ System.arraycopy(xba, 0, publicKeyEncoded, offset + keyLengthBytes
+ - xba.length, xba.length);
+ }
+ offset += keyLengthBytes;
+
+ BigInteger y = pubKey.getW().getAffineY();
+ byte[] yba = y.toByteArray();
+ if (yba.length > keyLengthBytes + 1 || yba.length == keyLengthBytes + 1
+ && yba[0] != 0) {
+ throw new IllegalStateException(
+ "Y coordinate of EC public key has wrong size");
+ }
+
+ if (yba.length == keyLengthBytes + 1) {
+ System.arraycopy(yba, 1, publicKeyEncoded, offset, keyLengthBytes);
+ } else {
+ System.arraycopy(yba, 0, publicKeyEncoded, offset + keyLengthBytes
+ - yba.length, yba.length);
+ }
+
+ return publicKeyEncoded;
+ }
+
+ public static ECPublicKey rawToEncodedECPublicKey(String curveName, byte[] rawBytes) throws
+ NoSuchAlgorithmException, InvalidKeySpecException, InvalidParameterSpecException {
+ KeyFactory kf = KeyFactory.getInstance(EC);
+ int mid = rawBytes.length / 2;
+ byte[] x = Arrays.copyOfRange(rawBytes, 0, mid);
+ byte[] y = Arrays.copyOfRange(rawBytes, mid, rawBytes.length);
+ ECPoint w = new ECPoint(new BigInteger(1, x), new BigInteger(1, y));
+ return (ECPublicKey) kf.generatePublic(new ECPublicKeySpec(w, ecParameterSpecForCurve(curveName)));
+ }
+
+ public static ECParameterSpec ecParameterSpecForCurve(String curveName) throws
+ NoSuchAlgorithmException, InvalidParameterSpecException {
+ AlgorithmParameters params = AlgorithmParameters.getInstance(EC);
+ params.init(new ECGenParameterSpec(curveName));
+ return params.getParameterSpec(ECParameterSpec.class);
+ }
+
+ public static byte[] generateSharedSecret(PrivateKey privateKey,
+ PublicKey publicKey) {
+ try {
+ KeyAgreement keyAgreement = KeyAgreement.getInstance(ECDH);
+ keyAgreement.init(privateKey);
+ keyAgreement.doPhase(publicKey, true);
+
+ return keyAgreement.generateSecret();
+ } catch (InvalidKeyException | NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ //==============================================================================================
+ // Single Scheduler all callback must be observed on
+ //==============================================================================================
+
+ private static final Scheduler SS = Schedulers.single();
+
+ public BleConnectionState getPatchConnectionState() {
+ return patch.getConnectionState();
+ }
+
+ public Observable observePatchConnectionState() {
+ return patch.observeConnectionState();
+ }
+
+ public void updateMacAddress(String mac, boolean b){
+ patch.updateMacAddress(mac, b);
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchStateManager.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchStateManager.java
new file mode 100644
index 0000000000..a41131e4db
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchStateManager.java
@@ -0,0 +1,290 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble;
+
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.interfaces.CommandQueue;
+import info.nightscout.androidaps.utils.rx.AapsSchedulers;
+import info.nightscout.shared.logging.AAPSLogger;
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.code.BolusType;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.FetchAlarmTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.InternalSuspendedTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.ReadBolusFinishTimeTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.ReadTempBasalFinishTimeTask;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.BolusCurrent;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.NormalBasal;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasal;
+import io.reactivex.rxjava3.core.Maybe;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+
+
+@Singleton
+public class PatchStateManager {
+
+ @Inject IPreferenceManager pm;
+ @Inject ReadBolusFinishTimeTask readBolusFinishTimeTask;
+ @Inject ReadTempBasalFinishTimeTask readTempBasalFinishTimeTask;
+ @Inject InternalSuspendedTask internalSuspendedTask;
+ @Inject FetchAlarmTask FETCH_ALARM;
+ @Inject CommandQueue commandQueue;
+ @Inject AAPSLogger aapsLogger;
+ @Inject AapsSchedulers aapsSchedulers;
+
+ @Inject
+ public PatchStateManager() {
+
+ }
+
+ public synchronized void updatePatchState(PatchState newState) {
+ Maybe.fromCallable(() -> newState).observeOn(Schedulers.single())
+ .doOnSuccess(patchState -> updatePatchStateInner(patchState))
+ .observeOn(aapsSchedulers.getMain())
+ .doOnSuccess(patchState -> aapsLogger.debug(LTag.PUMP, patchState.toString()))
+ .subscribe();
+ }
+
+ /* Schedulers.io() */
+ public synchronized void updatePatchStateInner(PatchState newState) {
+
+ final PatchState oldState = pm.getPatchState();
+
+ int diff = newState.currentTime() - oldState.currentTime();
+ if (0 <= diff && diff < 10) {
+ /* 10초 안에 같은 PatchState update 시 skip */
+ if (oldState.equalState(newState)) {
+ return;
+ }
+ } else if (-5 < diff && diff < 0) {
+ /* 이전 State 가 새로운 State 를 덮어 쓰는 것을 방지 -4초 까지 */
+ return;
+ }
+
+ newState.setUpdatedTimestamp(System.currentTimeMillis());
+
+ if (newState.isNewAlertAlarm()) {
+ FETCH_ALARM.enqueue();
+ }
+
+ if (newState.isPatchInternalSuspended()){
+ onPatchInternalSuspended(newState);
+ }
+
+ /* Normal Basal --------------------------------------------------------------------------------------------- */
+
+ if (newState.isNormalBasalAct()) {
+ if (oldState.isNormalBasalPaused()) {
+ // Resume --> onBasalResume
+ onBasalResumeState();
+
+ } else if (oldState.isNormalBasalAct() == false) {
+ // Start --> onBasalStarted
+ }
+ } else if (oldState.isNormalBasalPaused() == false && newState.isNormalBasalPaused()) {
+ if (newState.isTempBasalAct()) {
+ } else {
+ // pause
+
+ }
+ }
+
+ /* Temp Basal ------------------------------------------------------------------------------------------- */
+ if (newState.isTempBasalAct()) {
+ if (oldState.isTempBasalAct() == false) {
+ // Start
+ onTempBasalStartState();
+ }
+ }
+
+ boolean tempBasalStopped = false;
+ boolean tempBasalFinished = false;
+
+ if (newState.isTempBasalDone() && !newState.isPatchInternalSuspended()) {
+ tempBasalFinished = true;
+ }
+
+ if (oldState.isTempBasalDone() == false) {
+ if (newState.isTempBasalDone()) {
+ tempBasalStopped = true;
+
+ onTempBasalDoneState();
+ } else if (oldState.isTempBasalAct() && newState.isTempBasalAct() == false) {
+ tempBasalStopped = true;
+
+ onTempBasalCancelState();
+ }
+ }
+
+ if (tempBasalStopped) {
+ if (newState.isNormalBasalAct()) {
+ if (!newState.isPatchInternalSuspended()) {
+ onNormalBasalResumed(tempBasalFinished);
+ }
+ }
+ }
+
+ if (newState.isTempBasalAct() == false && pm.getTempBasalManager().getStartedBasal() != null) {
+ pm.getTempBasalManager().updateBasalStopped();
+ }
+
+ /* Now Bolus -------------------------------------------------------------------------------------------- */
+ if (oldState.isNowBolusRegAct() == false && newState.isNowBolusRegAct() == true) {
+ // Start
+ } else if (oldState.isNowBolusDone() == false) {
+ if (oldState.isNowBolusRegAct() && newState.isNowBolusRegAct() == false) {
+ // Cancel
+ } else if (newState.isNowBolusDone()) {
+ // Done
+ }
+ }
+
+ BolusCurrent bolusCurrent = pm.getBolusCurrent();
+
+ if (newState.isNowBolusRegAct() == false && bolusCurrent.historyId(BolusType.NOW) > 0
+ && bolusCurrent.endTimeSynced(BolusType.NOW)) {
+ bolusCurrent.clearBolus(BolusType.NOW);
+ }
+
+ /* Extended Bolus --------------------------------------------------------------------------------------- */
+ if (oldState.isExtBolusRegAct() == false && newState.isExtBolusRegAct() == true) {
+ // Start
+ } else if (oldState.isExtBolusDone() == false) {
+ if (oldState.isExtBolusRegAct() && newState.isExtBolusRegAct() == false) {
+ // Cancel
+ } else if (newState.isExtBolusDone()) {
+ // Done
+ }
+ }
+
+ if (newState.isExtBolusRegAct() == false && bolusCurrent.historyId(BolusType.EXT) > 0
+ && bolusCurrent.endTimeSynced(BolusType.EXT)) {
+ bolusCurrent.clearBolus(BolusType.EXT);
+ }
+
+ /* Finish Time Sync and remained insulin update*/
+ /* Bolus Done -> update finish time */
+ if (Stream.of(BolusType.NOW, BolusType.EXT).anyMatch(type ->
+ newState.isBolusDone(type) && !bolusCurrent.endTimeSynced(type))) {
+ readBolusFinishTime();
+ }
+
+ /* TempBasal Done -> update finish time */
+ if (tempBasalFinished) {
+ readTempBasalFinishTime();
+ }
+
+ /* Remained Insulin update */
+ if (newState.getRemainedInsulin() != oldState.getRemainedInsulin()) {
+ pm.getPatchConfig().setRemainedInsulin(newState.getRemainedInsulin());
+ pm.flushPatchConfig();
+ }
+
+ pm.getPatchState().update(newState);
+ pm.flushPatchState();
+ }
+
+ private void onTempBasalStartState() {
+ TempBasal tempBasal = pm.getTempBasalManager().getStartedBasal();
+
+ if (tempBasal != null) {
+ pm.getPatchConfig().updateTempBasalStarted();
+
+ NormalBasal normalBasal = pm.getNormalBasalManager().getNormalBasal();
+
+ if (normalBasal != null) {
+ pm.getNormalBasalManager().updateBasalPaused();
+ }
+
+ pm.flushPatchConfig();
+ pm.flushNormalBasalManager();
+ }
+ }
+
+ void onTempBasalDoneState() {
+ TempBasal tempBasal = pm.getTempBasalManager().getStartedBasal();
+
+ if (tempBasal != null) {
+ pm.getTempBasalManager().updateBasalStopped();
+ pm.flushTempBasalManager();
+ }
+ }
+
+ private void onTempBasalCancelState() {
+ TempBasal tempBasal = pm.getTempBasalManager().getStartedBasal();
+
+ if (tempBasal != null) {
+ pm.getTempBasalManager().updateBasalStopped();
+ pm.flushTempBasalManager();
+ }
+ }
+
+
+ private void readBolusFinishTime() {
+ readBolusFinishTimeTask.enqueue();
+ }
+
+ private void readTempBasalFinishTime() {
+ readTempBasalFinishTimeTask.enqueue();
+ }
+
+ private synchronized void onBasalResumeState() {
+
+ if (!pm.getNormalBasalManager().isStarted()) {
+ long timestamp = System.currentTimeMillis();
+ onBasalResumed(timestamp + 1000);
+ }
+ }
+
+ void onNormalBasalResumed(boolean tempBasalFinished) {
+ NormalBasal normalBasal = pm.getNormalBasalManager().getNormalBasal();
+ if (normalBasal != null) {
+ pm.getNormalBasalManager().updateBasalStarted();
+ normalBasal.updateNormalBasalIndex();
+ pm.flushNormalBasalManager();;
+ }
+ }
+
+ public synchronized void onBasalResumed(long timestamp) {
+ if (!pm.getNormalBasalManager().isStarted()) {
+ pm.getNormalBasalManager().updateBasalStarted();
+
+ pm.getPatchConfig().updateNormalBasalStarted();
+ pm.getPatchConfig().setNeedSetBasalSchedule(false);
+
+ NormalBasal basal = pm.getNormalBasalManager().getNormalBasal();
+
+ if (basal != null) {
+ basal.updateNormalBasalIndex();
+ }
+
+ pm.flushPatchConfig();
+ pm.flushNormalBasalManager();
+ }
+ }
+
+ public synchronized void onBasalStarted(NormalBasal basal, long timestamp) {
+ if (basal != null) {
+ pm.getNormalBasalManager().updateBasalStarted();
+ basal.updateNormalBasalIndex();
+ }
+
+ pm.getPatchConfig().updateNormalBasalStarted(); // updateNormalBasalStarted 도 동일함...
+ pm.getPatchConfig().setNeedSetBasalSchedule(false);
+
+ pm.flushPatchConfig();
+ pm.flushNormalBasalManager();
+ }
+
+ private void onPatchInternalSuspended(PatchState state) {
+ boolean isNowBolusActive = state.isNowBolusActive();
+ boolean isExtBolusActive = state.isExtBolusActive();
+ boolean isTempBasalActive = state.isTempBasalActive();
+
+ if (isNowBolusActive || isExtBolusActive || isTempBasalActive) {
+ internalSuspendedTask.enqueue(isNowBolusActive, isExtBolusActive, isTempBasalActive);
+ }
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PreferenceManager.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PreferenceManager.kt
new file mode 100644
index 0000000000..5ceeb6ce0c
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PreferenceManager.kt
@@ -0,0 +1,253 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble
+
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.pump.eopatch.GsonHelper
+import info.nightscout.androidaps.plugins.pump.eopatch.code.SettingKeys
+import info.nightscout.androidaps.plugins.pump.eopatch.code.PatchLifecycle
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.*
+import info.nightscout.shared.sharedPreferences.SP
+import io.reactivex.rxjava3.core.Observable
+import javax.inject.Inject
+import javax.inject.Singleton
+
+interface IPreferenceManager {
+ fun getPatchConfig(): PatchConfig
+ fun getPatchState(): PatchState
+ fun getBolusCurrent(): BolusCurrent
+ fun getNormalBasalManager(): NormalBasalManager
+ fun getTempBasalManager(): TempBasalManager
+ fun getAlarms(): Alarms
+ fun init()
+ fun flushPatchConfig()
+ fun flushPatchState()
+ fun flushBolusCurrent()
+ fun flushNormalBasalManager()
+ fun flushTempBasalManager()
+ fun flushAlarms()
+ fun updatePatchLifeCycle(event: PatchLifecycleEvent)
+ fun updatePatchState(newState: PatchState)
+ fun getPatchSerial(): String
+ fun getPatchMac(): String?
+ fun isActivated(): Boolean
+ fun setMacAddress(mac: String)
+ fun getPatchExpiredTime(): Long
+ fun setSharedKey(bytes: ByteArray?)
+ fun setSeq15(seq15: Int)
+ fun getSeq15(): Int
+ fun increaseSeq15()
+ fun getPatchWakeupTimestamp(): Long
+ fun observePatchLifeCycle(): Observable
+ fun observePatchConfig(): Observable
+ fun observePatchState(): Observable
+ fun observeBolusCurrent(): Observable
+ fun observeAlarm(): Observable
+ fun isInitDone(): Boolean
+}
+
+
+/**
+ * patch2 패키지에서 사용하는 프리퍼런스의 작업을 대신 처리하는 클래스
+ */
+@Singleton
+class PreferenceManager @Inject constructor(): IPreferenceManager {
+ @Inject lateinit var sp: SP
+ @Inject lateinit var rxBus: RxBus
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var mPatchConfig: PatchConfig
+ @Inject lateinit var mNormalBasalMgr: NormalBasalManager
+ @Inject lateinit var mTempBasalMgr: TempBasalManager
+ @Inject lateinit var mAlarms: Alarms
+
+ private var mPatchState = PatchState()
+ private var mBolusCurrent = BolusCurrent()
+ private lateinit var observePatchLifeCycle: Observable
+ private var initialized = false
+
+ @Inject
+ fun onInit() {
+ observePatchLifeCycle = mPatchConfig.observe()
+ .map { patchConfig -> patchConfig.lifecycleEvent.lifeCycle }
+ .distinctUntilChanged()
+ .replay(1).refCount()
+ }
+
+ override fun getPatchConfig(): PatchConfig {
+ return mPatchConfig
+ }
+
+ override fun getPatchState(): PatchState {
+ return mPatchState
+ }
+
+ override fun getBolusCurrent(): BolusCurrent {
+ return mBolusCurrent
+ }
+
+ override fun getNormalBasalManager(): NormalBasalManager {
+ return mNormalBasalMgr
+ }
+
+ override fun getTempBasalManager(): TempBasalManager {
+ return mTempBasalMgr
+ }
+
+ override fun getAlarms(): Alarms {
+ return mAlarms
+ }
+
+ override fun init() {
+ try {
+ val jsonStr = sp.getString(SettingKeys.PATCH_STATE, "")
+ val savedState = GsonHelper.sharedGson().fromJson(jsonStr, PatchState::class.java)
+ mPatchState = savedState
+ } catch (ex: Exception) {
+ mPatchState = PatchState()
+ aapsLogger.error(LTag.PUMP, ex.message?:"PatchState load error")
+ }
+
+ try {
+ val jsonStr = sp.getString(SettingKeys.BOLUS_CURRENT, "")
+ val savedBolusCurrent = GsonHelper.sharedGson().fromJson(jsonStr, BolusCurrent::class.java)
+ mBolusCurrent = savedBolusCurrent
+ } catch (ex: Exception) {
+ mBolusCurrent = BolusCurrent()
+ aapsLogger.error(LTag.PUMP, ex.message?:"BolusCurrent load error")
+ }
+
+ try {
+ val jsonStr = sp.getString(SettingKeys.PATCH_CONFIG, "")
+ val savedConfig = GsonHelper.sharedGson().fromJson(jsonStr, PatchConfig::class.java)
+ mPatchConfig.update(savedConfig)
+ } catch (ex: Exception) {
+ aapsLogger.error(LTag.PUMP, ex.message?:"PatchConfig load error")
+ }
+
+ try {
+ val jsonStr = sp.getString(SettingKeys.NORMAL_BASAL, "")
+ val normalBasalManager = GsonHelper.sharedGson().fromJson(jsonStr, NormalBasalManager::class.java)
+ mNormalBasalMgr.update(normalBasalManager)
+ } catch (ex: Exception) {
+ aapsLogger.error(LTag.PUMP, ex.message?:"NormalBasal load error")
+ }
+
+ try {
+ val jsonStr = sp.getString(SettingKeys.TEMP_BASAL, "")
+ val tempBasalManager = GsonHelper.sharedGson().fromJson(jsonStr, TempBasalManager::class.java)
+ mTempBasalMgr.update(tempBasalManager)
+ } catch (ex: Exception) {
+ aapsLogger.error(LTag.PUMP, ex.message?:"TempBasal load error")
+ }
+
+ try {
+ val jsonStr = sp.getString(SettingKeys.ALARMS, "")
+ val alarms = GsonHelper.sharedGson().fromJson(jsonStr, Alarms::class.java)
+ mAlarms.update(alarms)
+ } catch (ex: Exception) {
+ aapsLogger.error(LTag.PUMP, ex.message?:"Alarms load error")
+ }
+
+ aapsLogger.info(LTag.PUMP,"Load from PatchConfig preference: $mPatchConfig")
+ aapsLogger.info(LTag.PUMP,"Load from PatchState preference: $mPatchState")
+ aapsLogger.info(LTag.PUMP,"Load from BolusCurrent preference: $mBolusCurrent")
+ aapsLogger.info(LTag.PUMP,"Load from NormalBasal preference: $mNormalBasalMgr")
+ aapsLogger.info(LTag.PUMP,"Load from TempBasal preference: $mTempBasalMgr")
+ aapsLogger.info(LTag.PUMP,"Load from Alarms preference: $mAlarms")
+ initialized = true
+ }
+
+ override fun isInitDone() = initialized
+
+ override fun flushPatchConfig() = mPatchConfig.flush(sp)
+ override fun flushPatchState() = mPatchState.flush(sp)
+ override fun flushBolusCurrent() = mBolusCurrent.flush(sp)
+ override fun flushNormalBasalManager() = mNormalBasalMgr.flush(sp)
+ override fun flushTempBasalManager() = mTempBasalMgr.flush(sp)
+ override fun flushAlarms() = mAlarms.flush(sp)
+
+ @Synchronized
+ override fun updatePatchLifeCycle(event: PatchLifecycleEvent) {
+ mPatchConfig.updateLifecycle(event)
+ flushPatchConfig()
+
+ when (event.lifeCycle) {
+ PatchLifecycle.SHUTDOWN -> {
+ mPatchState.clear()
+ flushPatchState()
+ mBolusCurrent.clearAll()
+ flushBolusCurrent()
+ mTempBasalMgr.clear()
+ flushTempBasalManager()
+ }
+ else -> Unit
+ }
+
+ }
+
+ override fun updatePatchState(newState: PatchState) {
+ mPatchState = newState
+ flushPatchState()
+ }
+
+ override fun getPatchSerial(): String {
+ return mPatchConfig.patchSerialNumber
+ }
+
+ override fun getPatchMac(): String? {
+ return mPatchConfig.macAddress
+ }
+
+ override fun isActivated(): Boolean {
+ return mPatchConfig.isActivated
+ }
+
+ override fun setMacAddress(mac: String) {
+ mPatchConfig.macAddress = mac
+ flushPatchConfig()
+ }
+
+ override fun getPatchExpiredTime(): Long {
+ return mPatchConfig.getPatchExpiredTime()
+ }
+
+ override fun setSharedKey(bytes: ByteArray?) {
+ mPatchConfig.sharedKey = bytes
+ }
+
+ override fun setSeq15(seq15: Int) {
+ mPatchConfig.seq15 = seq15
+ }
+
+ override fun getSeq15(): Int {
+ return mPatchConfig.seq15
+ }
+
+ override fun increaseSeq15() {
+ mPatchConfig.incSeq()
+ }
+
+ override fun getPatchWakeupTimestamp(): Long {
+ return mPatchConfig.patchWakeupTimestamp
+ }
+
+ override fun observePatchLifeCycle(): Observable {
+ return observePatchLifeCycle
+ }
+
+ override fun observePatchConfig(): Observable {
+ return mPatchConfig.observe()
+ }
+
+ override fun observePatchState(): Observable {
+ return mPatchState.observe()
+ }
+
+ override fun observeBolusCurrent(): Observable{
+ return mBolusCurrent.observe()
+ }
+
+ override fun observeAlarm(): Observable {
+ return mAlarms.observe()
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ActivateTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ActivateTask.java
new file mode 100644
index 0000000000..f476a0ad8f
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ActivateTask.java
@@ -0,0 +1,42 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.SetKey;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BaseResponse;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.NormalBasal;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchLifecycleEvent;
+import io.reactivex.rxjava3.core.Single;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+
+@Singleton
+public class ActivateTask extends TaskBase {
+ @Inject StartNormalBasalTask startBasalTask;
+
+ private final SetKey SET_KEY = new SetKey();
+
+ @Inject
+ public ActivateTask() {
+ super(TaskFunc.ACTIVATE);
+ }
+
+ public Single start() {
+ NormalBasal enabled = pm.getNormalBasalManager().getNormalBasal();
+ return isReady()
+ .concatMapSingle(v -> SET_KEY.setKey())
+ .doOnNext(this::checkResponse)
+ .firstOrError()
+ .observeOn(Schedulers.io())
+ .flatMap(v -> startBasalTask.start(enabled))
+ .doOnSuccess(this::onActivated)
+ .map(BaseResponse::isSuccess)
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "ActivateTask error"));
+ }
+
+ private void onActivated(BaseResponse response) {
+ pm.updatePatchLifeCycle(PatchLifecycleEvent.createActivated());
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/BolusTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/BolusTask.java
new file mode 100644
index 0000000000..2896e250aa
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/BolusTask.java
@@ -0,0 +1,106 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.code.BolusExDuration;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.AppConstant;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.code.BolusType;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.util.FloatAdjusters;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.BolusCurrent;
+
+abstract class BolusTask extends TaskBase {
+
+ public BolusTask(TaskFunc func) {
+ super(func);
+ }
+
+ public void onQuickBolusStarted(float nowDoseU, float exDoseU, BolusExDuration exDuration) {
+ boolean now = (nowDoseU > 0);
+ boolean ext = (exDoseU > 0);
+
+ long startTimestamp = now ? System.currentTimeMillis() : 0;
+ long endTimestamp = startTimestamp + getPumpDuration(nowDoseU);
+
+ long nowHistoryID = 1L; //record no
+ long exStartTimestamp;
+
+ if (now) {
+ pm.getBolusCurrent().startNowBolus(nowHistoryID, nowDoseU, startTimestamp, endTimestamp);
+ }
+ if (ext) {
+ long estimatedExStartTimestamp;
+
+ if (now) {
+ exStartTimestamp = 0;
+ }
+ else {
+ estimatedExStartTimestamp = System.currentTimeMillis();
+ exStartTimestamp = estimatedExStartTimestamp;
+ }
+ long exEndTimestamp = exStartTimestamp + exDuration.milli();
+
+ long extHistoryID = 2L; //record no
+ pm.getBolusCurrent().startExtBolus(extHistoryID, exDoseU, exStartTimestamp,
+ exEndTimestamp, exDuration.milli());
+ }
+
+ pm.flushBolusCurrent();
+ }
+
+
+ public void onCalcBolusStarted(float nowDoseU) {
+ boolean now = (nowDoseU > 0);
+
+ long startTimestamp = now ? System.currentTimeMillis() : 0; // dm_1720
+ long endTimestamp = startTimestamp + getPumpDuration(nowDoseU);
+
+ long nowHistoryID = 1L; //record no
+
+ if (now) {
+ pm.getBolusCurrent().startNowBolus(nowHistoryID, nowDoseU, startTimestamp, endTimestamp);
+ }
+
+ pm.flushBolusCurrent();
+ }
+
+ public void updateNowBolusStopped(int injected) {
+ updateNowBolusStopped(injected, 0);
+ }
+
+ public void updateNowBolusStopped(int injected, long suspendedTimestamp) {
+ BolusCurrent bolusCurrent = pm.getBolusCurrent();
+ long nowID = bolusCurrent.historyId(BolusType.NOW);
+ if (nowID > 0 && !bolusCurrent.endTimeSynced(BolusType.NOW)) {
+ long stopTime = (suspendedTimestamp > 0) ? suspendedTimestamp : System.currentTimeMillis();
+ float injectedDoseU = FloatAdjusters.FLOOR2_BOLUS.apply(injected * AppConstant.INSULIN_UNIT_P);
+ bolusCurrent.getNowBolus().setInjected(injectedDoseU);
+ bolusCurrent.getNowBolus().setEndTimestamp(stopTime);
+ bolusCurrent.setEndTimeSynced(BolusType.NOW, true);
+ pm.flushBolusCurrent();
+ }
+ }
+
+ public void updateExtBolusStopped(int injected) {
+ updateExtBolusStopped(injected, 0);
+ }
+
+ public void updateExtBolusStopped(int injected, long suspendedTimestamp) {
+ BolusCurrent bolusCurrent = pm.getBolusCurrent();
+ long extID = bolusCurrent.historyId(BolusType.EXT);
+ if (extID > 0 && !bolusCurrent.endTimeSynced(BolusType.EXT)) {
+ long stopTime = (suspendedTimestamp > 0) ? suspendedTimestamp : System.currentTimeMillis();
+ float injectedDoseU = FloatAdjusters.FLOOR2_BOLUS.apply(injected * AppConstant.INSULIN_UNIT_P);
+ bolusCurrent.getExtBolus().setInjected(injectedDoseU);
+ bolusCurrent.getExtBolus().setEndTimestamp(stopTime);
+ bolusCurrent.setEndTimeSynced(BolusType.EXT, true);
+ pm.flushBolusCurrent();
+ }
+ }
+
+ private long getPumpDuration(float doseU) {
+ if (doseU > 0) {
+ long pumpDuration = pm.getPatchConfig().getPumpDurationSmallMilli();
+ return (long) ((doseU / AppConstant.BOLUS_UNIT_STEP) * pumpDuration);
+ }
+ return 0L;
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/DeactivateTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/DeactivateTask.java
new file mode 100644
index 0000000000..9765550713
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/DeactivateTask.java
@@ -0,0 +1,120 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager;
+import info.nightscout.androidaps.plugins.pump.eopatch.code.DeactivationStatus;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.DeActivation;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.code.BolusType;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.BolusCurrent;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchLifecycleEvent;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasal;
+import info.nightscout.androidaps.utils.rx.AapsSchedulers;
+import info.nightscout.shared.logging.LTag;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class DeactivateTask extends TaskBase {
+ @Inject StopBasalTask stopBasalTask;
+ @Inject IPreferenceManager pm;
+ @Inject AapsSchedulers aapsSchedulers;
+
+ private final DeActivation DEACTIVATION;
+
+ @Inject
+ public DeactivateTask() {
+ super(TaskFunc.DEACTIVATE);
+ DEACTIVATION = new DeActivation();
+ }
+
+ public Single run(boolean forced, long timeout) {
+ return isReadyCheckActivated()
+ .timeout(timeout, TimeUnit.MILLISECONDS)
+ .concatMapSingle(v ->
+ DEACTIVATION.start()
+ .doOnSuccess(this::checkResponse)
+ .observeOn(aapsSchedulers.getIo())
+ .doOnSuccess(response -> onDeactivated()))
+ .map(response -> DeactivationStatus.of(response.isSuccess(), forced))
+ .firstOrError()
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "DeactivateTask error"))
+ .onErrorResumeNext(e -> {
+ if (forced) {
+ try {
+ onDeactivated();
+ } catch (Exception t) {
+ aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "DeactivateTask error");
+ }
+ }
+
+ return Single.just(DeactivationStatus.of(false, forced));
+ });
+ }
+
+ private Observable isReadyCheckActivated() {
+ if (pm.getPatchConfig().isActivated()) {
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+
+ stopBasalTask.enqueue();
+
+ return isReady2();
+ }
+
+ return isReady();
+ }
+
+ private void onDeactivated() {
+ synchronized (lock) {
+ patch.updateMacAddress(null, false);
+
+ if (pm.getPatchConfig().getLifecycleEvent().isShutdown()) {
+ return;
+ }
+ cleanUpRepository();
+ pm.getNormalBasalManager().updateForDeactivation();
+ pm.updatePatchLifeCycle(PatchLifecycleEvent.createShutdown());
+
+ }
+ }
+
+ private void cleanUpRepository() {
+ updateNowBolusStopped();
+ updateExtBolusStopped();
+ updateTempBasalStopped();
+ }
+
+ private void updateTempBasalStopped() {
+ TempBasal tempBasal = pm.getTempBasalManager().getStartedBasal();
+
+ if (tempBasal != null) {
+ pm.getTempBasalManager().updateBasalStopped();
+ pm.flushTempBasalManager();
+ }
+ }
+
+ private void updateNowBolusStopped() {
+ BolusCurrent bolusCurrent = pm.getBolusCurrent();
+ long nowID = bolusCurrent.historyId(BolusType.NOW);
+
+ if (nowID > 0 && !bolusCurrent.endTimeSynced(BolusType.NOW)) {
+ bolusCurrent.setEndTimeSynced(BolusType.NOW, true);
+ pm.flushBolusCurrent();
+ }
+ }
+
+ private void updateExtBolusStopped() {
+ BolusCurrent bolusCurrent = pm.getBolusCurrent();
+ long extID = bolusCurrent.historyId(BolusType.EXT);
+
+ if (extID > 0 && !bolusCurrent.endTimeSynced(BolusType.EXT)) {
+ bolusCurrent.setEndTimeSynced(BolusType.EXT, true);
+ pm.flushBolusCurrent();
+ }
+ }
+
+
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/FetchAlarmTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/FetchAlarmTask.java
new file mode 100644
index 0000000000..ba6f1aa217
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/FetchAlarmTask.java
@@ -0,0 +1,47 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.bus.RxBus;
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmRegistry;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetErrorCodes;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.AeCodeResponse;
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class FetchAlarmTask extends TaskBase {
+ @Inject RxBus rxBus;
+ @Inject IAlarmRegistry alarmRegistry;
+
+ private final GetErrorCodes ALARM_ALERT_ERROR_CODE_GET;
+
+ @Inject
+ public FetchAlarmTask() {
+ super(TaskFunc.FETCH_ALARM);
+ ALARM_ALERT_ERROR_CODE_GET = new GetErrorCodes();
+ }
+
+ public Single getPatchAlarm() {
+ return isReady()
+ .concatMapSingle(v -> ALARM_ALERT_ERROR_CODE_GET.get())
+ .doOnNext(this::checkResponse)
+ .firstOrError()
+ .doOnSuccess(aeCodeResponse -> alarmRegistry.add(aeCodeResponse.getAlarmCodes()))
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "FetchAlarmTask error"));
+ }
+
+ public synchronized void enqueue() {
+ boolean ready = (disposable == null || disposable.isDisposed());
+
+ if (ready) {
+ disposable = getPatchAlarm()
+ .timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
+ .subscribe();
+ }
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/GetPatchInfoTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/GetPatchInfoTask.java
new file mode 100644
index 0000000000..15ee26f1c1
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/GetPatchInfoTask.java
@@ -0,0 +1,111 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetFirmwareVersion;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetLOT;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetModelName;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetPumpDuration;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetSerialNumber;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetWakeUpTime;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.SetGlobalTime;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BaseResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.FirmwareVersionResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.LotNumberResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.ModelNameResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PumpDurationResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.SerialNumberResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.WakeUpTimeResponse;
+
+import java.util.Arrays;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import io.reactivex.rxjava3.core.Single;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+
+@Singleton
+public class GetPatchInfoTask extends TaskBase {
+ @Inject UpdateConnectionTask updateConnectionTask;
+
+ private final SetGlobalTime SET_GLOBAL_TIME;
+ private final GetSerialNumber SERIAL_NUMBER_GET;
+ private final GetLOT LOT_NUMBER_GET;
+ private final GetFirmwareVersion FIRMWARE_VERSION_GET;
+ private final GetWakeUpTime WAKE_UP_TIME_GET;
+ private final GetPumpDuration PUMP_DURATION_GET;
+ private final GetModelName GET_MODEL_NAME;
+
+ @Inject
+ public GetPatchInfoTask() {
+ super(TaskFunc.GET_PATCH_INFO);
+
+ SET_GLOBAL_TIME = new SetGlobalTime();
+ SERIAL_NUMBER_GET = new GetSerialNumber();
+ LOT_NUMBER_GET = new GetLOT();
+ FIRMWARE_VERSION_GET = new GetFirmwareVersion();
+ WAKE_UP_TIME_GET = new GetWakeUpTime();
+ PUMP_DURATION_GET = new GetPumpDuration();
+ GET_MODEL_NAME = new GetModelName();
+ }
+
+ public Single get() {
+ Single tasks = Single.concat(Arrays.asList(
+ SET_GLOBAL_TIME.set(),
+ SERIAL_NUMBER_GET.get().doOnSuccess(this::onSerialNumberResponse),
+ LOT_NUMBER_GET.get().doOnSuccess(this::onLotNumberResponse),
+ FIRMWARE_VERSION_GET.get().doOnSuccess(this::onFirmwareResponse),
+ WAKE_UP_TIME_GET.get().doOnSuccess(this::onWakeupTimeResponse),
+ PUMP_DURATION_GET.get().doOnSuccess(this::onPumpDurationResponse),
+ GET_MODEL_NAME.get().doOnSuccess(this::onModelNameResponse)))
+ .map(BaseResponse::isSuccess)
+ .filter(v -> !v)
+ .first(true);
+
+ return isReady()
+ .concatMapSingle(it -> tasks)
+ .firstOrError()
+ .observeOn(Schedulers.io())
+ .doOnSuccess(this::onPatchWakeupSuccess)
+ .doOnError(this::onPatchWakeupFailed)
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "GetPatchInfoTask error"));
+ }
+
+ private void onSerialNumberResponse(SerialNumberResponse v) {
+ pm.getPatchConfig().setPatchSerialNumber(v.getSerialNumber());
+ }
+
+ private void onLotNumberResponse(LotNumberResponse v) {
+ pm.getPatchConfig().setPatchLotNumber(v.getLotNumber());
+ }
+
+ private void onFirmwareResponse(FirmwareVersionResponse v) {
+ pm.getPatchConfig().setPatchFirmwareVersion(v.getFirmwareVersionString());
+ }
+
+ private void onWakeupTimeResponse(WakeUpTimeResponse v) {
+ pm.getPatchConfig().setPatchWakeupTimestamp(v.getTimeInMillis());
+ }
+
+ private void onPumpDurationResponse(PumpDurationResponse v) {
+ pm.getPatchConfig().setPumpDurationLargeMilli(v.getDurationL() * 100L);
+ pm.getPatchConfig().setPumpDurationMediumMilli(v.getDurationM() * 100L);
+ pm.getPatchConfig().setPumpDurationSmallMilli(v.getDurationS() * 100L);
+ }
+
+ private void onModelNameResponse(ModelNameResponse modelNameResponse) {
+ pm.getPatchConfig().setPatchModelName(modelNameResponse.getModelName());
+ }
+
+ private void onPatchWakeupSuccess(Boolean result) {
+ synchronized (lock) {
+ pm.flushPatchConfig();
+ }
+ }
+
+ private void onPatchWakeupFailed(Throwable e) {
+ patch.setSeq(-1);
+ pm.getPatchConfig().updateDeactivated();
+ pm.flushPatchConfig();
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/InfoReminderTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/InfoReminderTask.java
new file mode 100644
index 0000000000..15dfe2dbff
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/InfoReminderTask.java
@@ -0,0 +1,46 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.InfoReminderSet;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class InfoReminderTask extends TaskBase {
+ @Inject IPreferenceManager pm;
+
+ private final InfoReminderSet INFO_REMINDER_SET;
+
+ @Inject
+ public InfoReminderTask() {
+ super(TaskFunc.INFO_REMINDER);
+ INFO_REMINDER_SET = new InfoReminderSet();
+ }
+
+ /* alert delay 사용안함 */
+ public Single set(boolean infoReminder) {
+ return isReady()
+ .concatMapSingle(v -> INFO_REMINDER_SET.set(infoReminder))
+ .doOnNext(this::checkResponse)
+ .firstOrError()
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "InfoReminderTask error"));
+ }
+
+ public synchronized void enqueue() {
+
+ boolean ready = (disposable == null || disposable.isDisposed());
+
+ if (ready) {
+ disposable = set(pm.getPatchConfig().getInfoReminder())
+ .timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
+ .subscribe();
+ }
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/InternalSuspendedTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/InternalSuspendedTask.java
new file mode 100644
index 0000000000..6ef7bd5b8b
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/InternalSuspendedTask.java
@@ -0,0 +1,124 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import android.os.SystemClock;
+
+import info.nightscout.androidaps.interfaces.PumpSync;
+import info.nightscout.androidaps.logging.UserEntryLogger;
+import info.nightscout.androidaps.utils.userEntry.UserEntryMapper;
+import info.nightscout.shared.logging.LTag;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.interfaces.CommandQueue;
+import info.nightscout.shared.logging.AAPSLogger;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetInternalSuspendTime;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchInternalSuspendTimeResponse;
+import info.nightscout.androidaps.queue.Callback;
+import info.nightscout.androidaps.queue.commands.Command;
+
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.Single;
+import io.reactivex.rxjava3.subjects.BehaviorSubject;
+
+@Singleton
+public class InternalSuspendedTask extends BolusTask {
+ @Inject CommandQueue commandQueue;
+ @Inject AAPSLogger aapsLogger;
+ @Inject PumpSync pumpSync;
+ @Inject UserEntryLogger uel;
+
+ private final GetInternalSuspendTime INTERNAL_SUSPEND_TIME_GET;
+ private final BehaviorSubject bolusCheckSubject = BehaviorSubject.create();
+ private final BehaviorSubject extendedBolusCheckSubject = BehaviorSubject.create();
+ private final BehaviorSubject basalCheckSubject = BehaviorSubject.create();
+
+ @Inject
+ public InternalSuspendedTask() {
+ super(TaskFunc.INTERNAL_SUSPEND);
+
+ INTERNAL_SUSPEND_TIME_GET = new GetInternalSuspendTime();
+ }
+
+ private Observable getBolusSubject(){
+ return bolusCheckSubject.hide();
+ }
+
+ private Observable getExtendedBolusSubject(){
+ return extendedBolusCheckSubject.hide();
+ }
+
+ private Observable getBasalSubject(){
+ return basalCheckSubject.hide();
+ }
+
+ public Single start(boolean isNowBolusActive, boolean isExtBolusActive, boolean isTempBasalActive) {
+ if (isNowBolusActive || isExtBolusActive) {
+ enqueue(TaskFunc.READ_BOLUS_FINISH_TIME);
+ }
+
+ if (isTempBasalActive) {
+ enqueue(TaskFunc.READ_TEMP_BASAL_FINISH_TIME);
+ }
+
+ if (commandQueue.isRunning(Command.CommandType.BOLUS)) {
+ uel.log(UserEntryMapper.Action.CANCEL_BOLUS, UserEntryMapper.Sources.EOPatch2);
+ commandQueue.cancelAllBoluses(null);
+ SystemClock.sleep(650);
+ }
+ bolusCheckSubject.onNext(true);
+
+ if (pumpSync.expectedPumpState().getExtendedBolus() != null) {
+ uel.log(UserEntryMapper.Action.CANCEL_EXTENDED_BOLUS, UserEntryMapper.Sources.EOPatch2);
+ commandQueue.cancelExtended(new Callback() {
+ @Override
+ public void run() {
+ extendedBolusCheckSubject.onNext(true);
+ }
+ });
+ }else{
+ extendedBolusCheckSubject.onNext(true);
+ }
+
+ if (pumpSync.expectedPumpState().getTemporaryBasal() != null) {
+ uel.log(UserEntryMapper.Action.CANCEL_TEMP_BASAL, UserEntryMapper.Sources.EOPatch2);
+ commandQueue.cancelTempBasal(true, new Callback() {
+ @Override
+ public void run() {
+ basalCheckSubject.onNext(true);
+ }
+ });
+ }else{
+ basalCheckSubject.onNext(true);
+ }
+
+ return Observable.zip(getBolusSubject(), getExtendedBolusSubject(), getBasalSubject(),
+ (bolusReady, extendedBolusReady, basalReady) -> (bolusReady && extendedBolusReady && basalReady))
+ .filter(ready -> ready)
+ .flatMap(v -> isReady())
+ .concatMapSingle(v -> getInternalSuspendTime())
+ .firstOrError()
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "InternalSuspendedTask error"));
+ }
+
+ private Single getInternalSuspendTime() {
+ return INTERNAL_SUSPEND_TIME_GET.get()
+ .doOnSuccess(this::checkResponse)
+ .map(PatchInternalSuspendTimeResponse::getTotalSeconds);
+ }
+
+ public synchronized void enqueue(boolean isNowBolusActive, boolean isExtBolusActive, boolean isTempBasalActive) {
+ boolean ready = (disposable == null || disposable.isDisposed());
+
+ if (ready) {
+ disposable = start(isNowBolusActive, isExtBolusActive, isTempBasalActive)
+ .timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
+ .subscribe(v -> {
+ bolusCheckSubject.onNext(false);
+ extendedBolusCheckSubject.onNext(false);
+ basalCheckSubject.onNext(false);
+ });
+ }
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/NeedleSensingTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/NeedleSensingTask.java
new file mode 100644
index 0000000000..d9a1a7451a
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/NeedleSensingTask.java
@@ -0,0 +1,49 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode;
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmRegistry;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.StartNeedleCheck;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.UpdateConnection;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
+import info.nightscout.shared.logging.LTag;
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class NeedleSensingTask extends TaskBase {
+ @Inject IAlarmRegistry alarmRegistry;
+
+ StartNeedleCheck START_NEEDLE_CHECK;
+ UpdateConnection UPDATE_CONNECTION;
+
+ @Inject
+ public NeedleSensingTask() {
+ super(TaskFunc.NEEDLE_SENSING);
+ START_NEEDLE_CHECK = new StartNeedleCheck();
+ UPDATE_CONNECTION = new UpdateConnection();
+ }
+
+ public Single start() {
+
+ return isReady()
+ .concatMapSingle(v -> START_NEEDLE_CHECK.start())
+ .doOnNext(this::checkResponse)
+ .concatMapSingle(v -> UPDATE_CONNECTION.get())
+ .doOnNext(this::checkResponse)
+ .map(updateConnectionResponse -> PatchState.Companion.create(updateConnectionResponse.getPatchState(), System.currentTimeMillis()))
+ .doOnNext(this::onResponse)
+ .map(patchState -> !patchState.isNeedNeedleSensing())
+ .firstOrError()
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "NeedleSensingTask error"));
+ }
+
+ private void onResponse(PatchState v) {
+ if (v.isNeedNeedleSensing()) {
+ alarmRegistry.add(AlarmCode.A016, 0, false).subscribe();
+ } else {
+ alarmRegistry.remove(AlarmCode.A016).subscribe();
+ }
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/PauseBasalTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/PauseBasalTask.java
new file mode 100644
index 0000000000..bdbd28b964
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/PauseBasalTask.java
@@ -0,0 +1,163 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+
+import android.os.SystemClock;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.interfaces.CommandQueue;
+import info.nightscout.androidaps.interfaces.PumpSync;
+import info.nightscout.androidaps.logging.UserEntryLogger;
+import info.nightscout.androidaps.utils.userEntry.UserEntryMapper;
+import info.nightscout.shared.logging.AAPSLogger;
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode;
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmRegistry;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BasalPause;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
+import info.nightscout.androidaps.queue.Callback;
+import info.nightscout.androidaps.queue.commands.Command;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.Single;
+import io.reactivex.rxjava3.subjects.BehaviorSubject;
+
+@Singleton
+public class PauseBasalTask extends BolusTask {
+ @Inject IAlarmRegistry alarmRegistry;
+ @Inject IPreferenceManager pm;
+ @Inject CommandQueue commandQueue;
+ @Inject AAPSLogger aapsLogger;
+ @Inject PumpSync pumpSync;
+ @Inject UserEntryLogger uel;
+
+ private final BasalPause BASAL_PAUSE;
+
+ private final BehaviorSubject bolusCheckSubject = BehaviorSubject.create();
+ private final BehaviorSubject extendedBolusCheckSubject = BehaviorSubject.create();
+ private final BehaviorSubject basalCheckSubject = BehaviorSubject.create();
+
+ @Inject
+ public PauseBasalTask() {
+ super(TaskFunc.PAUSE_BASAL);
+
+ BASAL_PAUSE = new BasalPause();
+ }
+
+ private Observable getBolusSubject(){
+ return bolusCheckSubject.hide();
+ }
+
+ private Observable getExtendedBolusSubject(){
+ return extendedBolusCheckSubject.hide();
+ }
+
+ private Observable getBasalSubject(){
+ return basalCheckSubject.hide();
+ }
+
+ public Single pause(float pauseDurationHour, long pausedTimestamp, @Nullable AlarmCode alarmCode) {
+ PatchState patchState = pm.getPatchState();
+
+ if(patchState.isNormalBasalPaused())
+ return Single.just(new PatchBooleanResponse(true));
+
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+
+ if (commandQueue.isRunning(Command.CommandType.BOLUS)) {
+ uel.log(UserEntryMapper.Action.CANCEL_BOLUS, UserEntryMapper.Sources.EOPatch2);
+ commandQueue.cancelAllBoluses(null);
+ SystemClock.sleep(650);
+ }
+ bolusCheckSubject.onNext(true);
+
+ if (pumpSync.expectedPumpState().getExtendedBolus() != null) {
+ uel.log(UserEntryMapper.Action.CANCEL_EXTENDED_BOLUS, UserEntryMapper.Sources.EOPatch2);
+ commandQueue.cancelExtended(new Callback() {
+ @Override
+ public void run() {
+ extendedBolusCheckSubject.onNext(true);
+ }
+ });
+ }else{
+ extendedBolusCheckSubject.onNext(true);
+ }
+
+ if (pumpSync.expectedPumpState().getTemporaryBasal() != null) {
+ uel.log(UserEntryMapper.Action.CANCEL_TEMP_BASAL, UserEntryMapper.Sources.EOPatch2);
+ commandQueue.cancelTempBasal(true, new Callback() {
+ @Override
+ public void run() {
+ basalCheckSubject.onNext(true);
+ }
+ });
+ }else{
+ basalCheckSubject.onNext(true);
+ }
+
+ return Observable.zip(getBolusSubject(), getExtendedBolusSubject(), getBasalSubject(),
+ (bolusReady, extendedBolusReady, basalReady) -> (bolusReady && extendedBolusReady && basalReady))
+ .filter(ready -> ready)
+ .flatMap(v -> isReady())
+ .concatMapSingle(v -> getSuspendedTime(pausedTimestamp))
+ .concatMapSingle(suspendedTimestamp -> pauseBasal(pauseDurationHour, alarmCode))
+ .firstOrError()
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "PauseBasalTask error"));
+ }
+
+ private Single getSuspendedTime(long pausedTimestamp) {
+ return Single.just(pausedTimestamp);
+ }
+
+ private Single pauseBasal(float pauseDurationHour, @Nullable AlarmCode alarmCode) {
+ if(alarmCode == null) {
+ return BASAL_PAUSE.pause(pauseDurationHour)
+ .doOnSuccess(this::checkResponse)
+ .doOnSuccess(v -> onBasalPaused(pauseDurationHour, null));
+ }
+
+ // 정지 알람 발생 시 basal pause 커맨드 전달하지 않음 - 주입 정지 이력만 생성
+ onBasalPaused(pauseDurationHour, alarmCode);
+
+ return Single.just(new PatchBooleanResponse(true));
+ }
+
+ private void onBasalPaused(float pauseDurationHour, @Nullable AlarmCode alarmCode) {
+ if (!pm.getNormalBasalManager().isSuspended()) {
+ if (alarmCode != null) {
+ pm.getPatchConfig().updateNormalBasalPausedSilently();
+ }
+ else {
+ pm.getPatchConfig().updateNormalBasalPaused(pauseDurationHour);
+ }
+ pm.getNormalBasalManager().updateBasalSuspended();
+
+ pm.flushNormalBasalManager();
+ pm.flushPatchConfig();
+
+ if((alarmCode == null || alarmCode.getType() == AlarmCode.TYPE_ALERT) && pauseDurationHour != 0)
+ alarmRegistry.add(AlarmCode.B001, TimeUnit.MINUTES.toMillis((long)(pauseDurationHour * 60)), false).subscribe();
+ }
+
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+ }
+
+ public synchronized void enqueue(float pauseDurationHour, long pausedTime, @Nullable AlarmCode alarmCode) {
+ boolean ready = (disposable == null || disposable.isDisposed());
+
+ if (ready) {
+ disposable = pause(pauseDurationHour, pausedTime, alarmCode)
+ .timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
+ .subscribe(v -> {
+ bolusCheckSubject.onNext(false);
+ extendedBolusCheckSubject.onNext(false);
+ basalCheckSubject.onNext(false);
+ });
+ }
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/PrimingTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/PrimingTask.java
new file mode 100644
index 0000000000..f9e0014856
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/PrimingTask.java
@@ -0,0 +1,54 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.StartPriming;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.UpdateConnection;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
+import io.reactivex.rxjava3.core.Observable;
+
+@Singleton
+public class PrimingTask extends TaskBase {
+ private final UpdateConnection UPDATE_CONNECTION;
+ private final StartPriming START_PRIMING;
+
+ @Inject
+ public PrimingTask() {
+ super(TaskFunc.PRIMING);
+
+ UPDATE_CONNECTION = new UpdateConnection();
+ START_PRIMING = new StartPriming();
+ }
+
+ public Observable start(long count) {
+ return isReady().concatMapSingle(v -> START_PRIMING.start())
+ .doOnNext(this::checkResponse)
+ .flatMap(v -> observePrimingSuccess(count))
+ .takeUntil(value -> (value == count))
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "PrimingTask error"));
+ }
+
+ private Observable observePrimingSuccess(long count) {
+
+ return Observable.merge(
+ Observable.interval(1, TimeUnit.SECONDS).take(count + 10)
+ .map(v -> v * 3)
+ .doOnNext(v -> {
+ if (v >= count) {
+ throw new Exception("Priming failed");
+ }
+ }),
+
+ Observable.interval(3, TimeUnit.SECONDS)
+ .concatMapSingle(v -> UPDATE_CONNECTION.get())
+ .map(response -> PatchState.Companion.create(response.getPatchState(), System.currentTimeMillis()))
+ .filter(PatchState::isPrimingSuccess)
+ .map(result -> count)
+ );
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ReadBolusFinishTimeTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ReadBolusFinishTimeTask.java
new file mode 100644
index 0000000000..3e73ecd354
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ReadBolusFinishTimeTask.java
@@ -0,0 +1,63 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BolusFinishTimeGet;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusFinishTimeResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.code.BolusType;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.BolusCurrent;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class ReadBolusFinishTimeTask extends BolusTask {
+ private final BolusFinishTimeGet BOLUS_FINISH_TIME_GET;
+
+ @Inject
+ public ReadBolusFinishTimeTask() {
+ super(TaskFunc.READ_BOLUS_FINISH_TIME);
+ BOLUS_FINISH_TIME_GET = new BolusFinishTimeGet();
+ }
+
+ Single read() {
+ return isReady()
+ .concatMapSingle(v -> BOLUS_FINISH_TIME_GET.get())
+ .firstOrError()
+ .doOnSuccess(this::checkResponse)
+ .doOnSuccess(this::onResponse)
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "ReadBolusFinishTimeTask error"));
+ }
+
+ void onResponse(BolusFinishTimeResponse response) {
+ PatchState patchState = pm.getPatchState();
+ BolusCurrent bolusCurrent = pm.getBolusCurrent();
+ long nowHistoryID = bolusCurrent.historyId(BolusType.NOW);
+ long extHistoryID = bolusCurrent.historyId(BolusType.EXT);
+
+ if (nowHistoryID > 0 && patchState.isBolusDone(BolusType.NOW) && response.getNowBolusFinishTime() > 0) {
+ bolusCurrent.setEndTimeSynced(BolusType.NOW, true);
+ enqueue(TaskFunc.STOP_NOW_BOLUS);
+ }
+
+ if (extHistoryID > 0 && patchState.isBolusDone(BolusType.EXT) && response.getExtBolusFinishTime() > 0) {
+ bolusCurrent.setEndTimeSynced(BolusType.EXT, true);
+ enqueue(TaskFunc.STOP_EXT_BOLUS);
+ }
+
+ pm.flushBolusCurrent();
+ }
+
+ public synchronized void enqueue() {
+ boolean ready = (disposable == null || disposable.isDisposed());
+
+ if (ready) {
+ disposable = read()
+ .timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
+ .subscribe();
+ }
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ReadTempBasalFinishTimeTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ReadTempBasalFinishTimeTask.java
new file mode 100644
index 0000000000..21fa18feb3
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ReadTempBasalFinishTimeTask.java
@@ -0,0 +1,45 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.TempBasalFinishTimeGet;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TempBasalFinishTimeResponse;
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class ReadTempBasalFinishTimeTask extends TaskBase {
+ private final TempBasalFinishTimeGet TEMP_BASAL_FINISH_TIME_GET;
+
+ @Inject
+ public ReadTempBasalFinishTimeTask() {
+ super(TaskFunc.READ_TEMP_BASAL_FINISH_TIME);
+ TEMP_BASAL_FINISH_TIME_GET = new TempBasalFinishTimeGet();
+ }
+
+ public Single read() {
+ return isReady()
+ .concatMapSingle(v -> TEMP_BASAL_FINISH_TIME_GET.get())
+ .firstOrError()
+ .doOnSuccess(this::checkResponse)
+ .doOnSuccess(this::onResponse)
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "ReadTempBasalFinishTimeTask error"));
+ }
+
+ private void onResponse(TempBasalFinishTimeResponse response) {
+ }
+
+ public synchronized void enqueue() {
+ boolean ready = (disposable == null || disposable.isDisposed());
+
+ if (ready) {
+ disposable = read()
+ .timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
+ .subscribe();
+ }
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ResumeBasalTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ResumeBasalTask.java
new file mode 100644
index 0000000000..2a1d2f6870
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ResumeBasalTask.java
@@ -0,0 +1,59 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode;
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmRegistry;
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.PatchStateManager;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BasalResume;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BaseResponse;
+
+import java.sql.SQLException;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.NormalBasal;
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class ResumeBasalTask extends TaskBase {
+ @Inject IAlarmRegistry alarmRegistry;
+ @Inject StartNormalBasalTask startNormalBasalTask;
+ @Inject PatchStateManager patchStateManager;
+
+ private final BasalResume BASAL_RESUME;
+
+ @Inject
+ public ResumeBasalTask() {
+ super(TaskFunc.RESUME_BASAL);
+ BASAL_RESUME = new BasalResume();
+ }
+
+ public synchronized Single extends BaseResponse> resume() {
+ if (pm.getPatchConfig().getNeedSetBasalSchedule()) {
+ return startNormalBasalTask.start(pm.getNormalBasalManager().getNormalBasal());
+ }
+
+ return isReady().concatMapSingle(v -> BASAL_RESUME.resume())
+ .doOnNext(this::checkResponse)
+ .firstOrError()
+ .doOnSuccess(v -> onResumeResponse(v))
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "ResumeBasalTask error"));
+ }
+
+ private void onResumeResponse(PatchBooleanResponse v) {
+ if (v.isSuccess()) {
+ patchStateManager.onBasalResumed(v.getTimestamp() + 1000);
+ alarmRegistry.remove(AlarmCode.B001).subscribe();
+ }
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+ }
+
+ @Override
+ protected void preCondition() throws Exception {
+ checkPatchActivated();
+ checkPatchConnected();
+ }
+
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SelfTestTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SelfTestTask.java
new file mode 100644
index 0000000000..05a990fe66
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SelfTestTask.java
@@ -0,0 +1,50 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.PatchSelfTestResult;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetGlobalTime;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetTemperature;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetVoltageLevelB4Priming;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BatteryVoltageLevelPairingResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.GlobalTimeResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TemperatureResponse;
+
+import java.util.Arrays;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class SelfTestTask extends TaskBase {
+ private final GetTemperature TEMPERATURE_GET;
+ private final GetVoltageLevelB4Priming BATTERY_LEVEL_GET_BEFORE_PRIMING;
+ private final GetGlobalTime GET_GLOBAL_TIME;
+
+ @Inject
+ public SelfTestTask() {
+ super(TaskFunc.SELF_TEST);
+
+ TEMPERATURE_GET = new GetTemperature();
+ BATTERY_LEVEL_GET_BEFORE_PRIMING = new GetVoltageLevelB4Priming();
+ GET_GLOBAL_TIME = new GetGlobalTime();
+ }
+
+ public Single start() {
+ Single tasks = Single.concat(Arrays.asList(
+ TEMPERATURE_GET.get()
+ .map(TemperatureResponse::getResult),
+ BATTERY_LEVEL_GET_BEFORE_PRIMING.get()
+ .map(BatteryVoltageLevelPairingResponse::getResult),
+ GET_GLOBAL_TIME.get(false)
+ .map(GlobalTimeResponse::getResult)))
+ .filter(result -> result != PatchSelfTestResult.TEST_SUCCESS)
+ .first(PatchSelfTestResult.TEST_SUCCESS);
+
+ return isReady()
+ .concatMapSingle(v -> tasks)
+ .firstOrError()
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "SelfTestTask error"));
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SetGlobalTimeTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SetGlobalTimeTask.java
new file mode 100644
index 0000000000..cfbd3701e6
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SetGlobalTimeTask.java
@@ -0,0 +1,73 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetGlobalTime;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.SetGlobalTime;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.GlobalTimeResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
+
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class SetGlobalTimeTask extends TaskBase {
+ private final SetGlobalTime SET_GLOBAL_TIME;
+ private final GetGlobalTime GET_GLOBAL_TIME;
+
+ @Inject
+ public SetGlobalTimeTask() {
+ super(TaskFunc.SET_GLOBAL_TIME);
+
+ SET_GLOBAL_TIME = new SetGlobalTime();
+ GET_GLOBAL_TIME = new GetGlobalTime();
+ }
+
+ public Single set() {
+ return isReady()
+ .concatMapSingle(v -> GET_GLOBAL_TIME.get(false))
+ .doOnNext(this::checkResponse)
+ .doOnNext(this::checkPatchTime)
+ .concatMapSingle(v -> SET_GLOBAL_TIME.set())
+ .doOnNext(this::checkResponse)
+ .firstOrError()
+ .doOnSuccess(v -> onSuccess())
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "SetGlobalTimeTask error"));
+ }
+
+ private void checkPatchTime(GlobalTimeResponse response) throws Exception {
+
+ long newMilli = System.currentTimeMillis();
+ long oldMilli = response.getGlobalTimeInMilli();
+ long oldOffset = response.getTimeZoneOffset();
+ int offset = TimeZone.getDefault().getOffset(newMilli);
+ int minutes = (int) TimeUnit.MILLISECONDS.toMinutes(offset);
+ int newOffset = minutes / 15;
+
+ long diff = Math.abs(oldMilli - newMilli);
+
+ if (diff > 60000 || oldOffset != newOffset) {
+ aapsLogger.debug(LTag.PUMPCOMM, String.format("checkPatchTime %s %s %s", diff, oldOffset, newOffset));
+ return;
+ }
+
+ throw new Exception("No time set required");
+ }
+
+ public synchronized void enqueue() {
+ boolean ready = (disposable == null || disposable.isDisposed());
+
+ if (ready) {
+ disposable = set()
+ .timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
+ .subscribe(v -> {}, e -> {}); // Exception 을 사용하기에...
+ }
+ }
+
+ private void onSuccess() {
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SetLowReservoirTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SetLowReservoirTask.java
new file mode 100644
index 0000000000..ea719bface
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SetLowReservoirTask.java
@@ -0,0 +1,55 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.SetLowReservoirLevelAndExpireAlert;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class SetLowReservoirTask extends TaskBase {
+ @Inject IPreferenceManager pm;
+
+ private final SetLowReservoirLevelAndExpireAlert SET_LOW_RESERVOIR_N_EXPIRE_ALERT;
+
+ @Inject
+ public SetLowReservoirTask() {
+ super(TaskFunc.LOW_RESERVOIR);
+ SET_LOW_RESERVOIR_N_EXPIRE_ALERT = new SetLowReservoirLevelAndExpireAlert();
+ }
+
+ public Single set(int doseUnit, int hours) {
+ return isReady()
+ .concatMapSingle(v -> SET_LOW_RESERVOIR_N_EXPIRE_ALERT.set(
+ doseUnit,
+ hours))
+ .doOnNext(this::checkResponse)
+ .firstOrError()
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "SetLowReservoirTask error"));
+ }
+
+ public synchronized void enqueue() {
+
+ int alertTime = pm.getPatchConfig().getPatchExpireAlertTime();
+ int alertSetting = pm.getPatchConfig().getLowReservoirAlertAmount();
+
+ boolean ready = (disposable == null || disposable.isDisposed());
+
+ if (ready) {
+ disposable = set(alertSetting, alertTime)
+ .timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
+ .subscribe();
+ }
+ }
+
+ @Override
+ protected void preCondition() throws Exception {
+ checkPatchConnected();
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartBondTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartBondTask.java
new file mode 100644
index 0000000000..0924de25af
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartBondTask.java
@@ -0,0 +1,55 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import android.bluetooth.BluetoothDevice;
+
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.StartBonding;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import io.reactivex.rxjava3.core.Single;
+
+import static info.nightscout.androidaps.plugins.pump.eopatch.core.api.StartBonding.OPTION_NUMERIC;
+
+@Singleton
+public class StartBondTask extends TaskBase {
+ private final StartBonding START_BOND;
+
+ @Inject
+ public StartBondTask() {
+ super(TaskFunc.START_BOND);
+ START_BOND = new StartBonding();
+ }
+
+ public Single start(String mac) {
+ prefSetMacAddress(mac);
+ patch.updateMacAddress(mac, false);
+
+ return isReady()
+ .concatMapSingle(v -> START_BOND.start(OPTION_NUMERIC))
+ .doOnNext(this::checkResponse)
+ .concatMap(response -> patch.observeBondState())
+ .doOnNext(state -> {
+ if(state == BluetoothDevice.BOND_NONE) throw new Exception();
+ })
+ .filter(result -> result == BluetoothDevice.BOND_BONDED)
+ .map(result -> true)
+ .timeout(60, TimeUnit.SECONDS)
+ .doOnNext(v -> prefSetMacAddress(mac))
+ .doOnError(e -> {
+ prefSetMacAddress("");
+ aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StartBondTask error");
+ })
+ .firstOrError();
+ }
+
+ private synchronized void prefSetMacAddress(String mac) {
+ pm.getPatchConfig().setMacAddress(mac);
+ }
+}
+
+
+
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartCalcBolusTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartCalcBolusTask.java
new file mode 100644
index 0000000000..022b9c9f8b
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartCalcBolusTask.java
@@ -0,0 +1,46 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.androidaps.data.DetailedBolusInfo;
+import info.nightscout.shared.logging.LTag;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BolusStart;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusResponse;
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class StartCalcBolusTask extends BolusTask {
+ private final BolusStart NOW_BOLUS_START;
+
+ @Inject
+ public StartCalcBolusTask() {
+ super(TaskFunc.START_CALC_BOLUS);
+
+ NOW_BOLUS_START = new BolusStart();
+ }
+
+ public Single extends BolusResponse> start(DetailedBolusInfo detailedBolusInfo) {
+ return isReady().concatMapSingle(v -> startBolusImpl((float)detailedBolusInfo.insulin))
+ .doOnNext(this::checkResponse)
+ .firstOrError()
+ .doOnSuccess(v -> onSuccess((float)detailedBolusInfo.insulin))
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StartCalcBolusTask error"));
+ }
+
+ private Single extends BolusResponse> startBolusImpl(float nowDoseU) {
+ return NOW_BOLUS_START.start(nowDoseU);
+ }
+
+ private void onSuccess(float nowDoseU) {
+ onCalcBolusStarted(nowDoseU);
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+ }
+
+ @Override
+ protected void preCondition() throws Exception {
+ checkPatchConnected();
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartNormalBasalTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartNormalBasalTask.java
new file mode 100644
index 0000000000..3adb07b441
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartNormalBasalTask.java
@@ -0,0 +1,53 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.PatchStateManager;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BasalScheduleSetBig;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BasalScheduleSetResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.NormalBasal;
+import info.nightscout.androidaps.utils.rx.AapsSchedulers;
+import info.nightscout.shared.logging.LTag;
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class StartNormalBasalTask extends TaskBase {
+ private final BasalScheduleSetBig BASAL_SCHEDULE_SET_BIG;
+
+ @Inject PatchStateManager patchStateManager;
+ @Inject AapsSchedulers aapsSchedulers;
+
+ @Inject
+ public StartNormalBasalTask() {
+ super(TaskFunc.START_NORMAL_BASAL);
+ BASAL_SCHEDULE_SET_BIG = new BasalScheduleSetBig();
+ }
+
+ public Single start(NormalBasal basal) {
+ return isReady().concatMapSingle(v -> startJob(basal)).firstOrError();
+ }
+
+ public Single startJob(NormalBasal basal) {
+ return BASAL_SCHEDULE_SET_BIG.set(basal.getDoseUnitPerSegmentArray())
+ .doOnSuccess(this::checkResponse)
+ .observeOn(aapsSchedulers.getIo())
+ .doOnSuccess(v -> onStartNormalBasalResponse(v, basal))
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StartNormalBasalTask error"));
+ }
+
+ private void onStartNormalBasalResponse(BasalScheduleSetResponse response, NormalBasal basal) {
+
+ long timeStamp = response.getTimestamp();
+ patchStateManager.onBasalStarted(basal, timeStamp+1000);
+
+ pm.getNormalBasalManager().setNormalBasal(basal);
+ pm.flushNormalBasalManager();
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+ }
+
+ @Override
+ protected void preCondition() throws Exception {
+ checkPatchConnected();
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartQuickBolusTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartQuickBolusTask.java
new file mode 100644
index 0000000000..bf662cad68
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartQuickBolusTask.java
@@ -0,0 +1,61 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.code.BolusExDuration;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BolusStart;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.ComboBolusStart;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.ExtBolusStart;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusResponse;
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class StartQuickBolusTask extends BolusTask {
+ private final BolusStart NOW_BOLUS_START;
+ private final ExtBolusStart EXT_BOLUS_START;
+ private final ComboBolusStart COMBO_BOLUS_START;
+
+ @Inject
+ public StartQuickBolusTask() {
+ super(TaskFunc.START_QUICK_BOLUS);
+
+ NOW_BOLUS_START = new BolusStart();
+ EXT_BOLUS_START = new ExtBolusStart();
+ COMBO_BOLUS_START = new ComboBolusStart();
+ }
+
+ public Single extends BolusResponse> start(float nowDoseU, float exDoseU,
+ BolusExDuration exDuration) {
+ return isReady().concatMapSingle(v -> startBolusImpl(nowDoseU, exDoseU, exDuration))
+ .doOnNext(this::checkResponse)
+ .firstOrError()
+ .doOnSuccess(v -> onSuccess(nowDoseU, exDoseU, exDuration))
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StartQuickBolusTask error"));
+ }
+
+ private Single extends BolusResponse> startBolusImpl(float nowDoseU, float exDoseU,
+ BolusExDuration exDuration) {
+ if (nowDoseU > 0 && exDoseU > 0) {
+ return COMBO_BOLUS_START.start(nowDoseU, exDoseU, exDuration.getMinute());
+ } else if (exDoseU > 0) {
+ return EXT_BOLUS_START.start(exDoseU, exDuration.getMinute());
+ } else {
+ return NOW_BOLUS_START.start(nowDoseU);
+ }
+ }
+
+ private void onSuccess(float nowDoseU, float exDoseU, BolusExDuration exDuration) {
+ onQuickBolusStarted(nowDoseU, exDoseU, exDuration);
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+ }
+
+ @Override
+ protected void preCondition() throws Exception {
+ //checkPatchActivated();
+ checkPatchConnected();
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartTempBasalTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartTempBasalTask.java
new file mode 100644
index 0000000000..e496d5e9f5
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartTempBasalTask.java
@@ -0,0 +1,48 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.TempBasalScheduleStart;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TempBasalScheduleSetResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasal;
+import info.nightscout.androidaps.utils.rx.AapsSchedulers;
+import info.nightscout.shared.logging.LTag;
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class StartTempBasalTask extends TaskBase {
+ @Inject IPreferenceManager pm;
+ @Inject AapsSchedulers aapsSchedulers;
+
+ private final TempBasalScheduleStart TEMP_BASAL_SCHEDULE_START;
+
+ @Inject
+ public StartTempBasalTask() {
+ super(TaskFunc.START_TEMP_BASAL);
+
+ TEMP_BASAL_SCHEDULE_START = new TempBasalScheduleStart();
+ }
+
+ public Single start(TempBasal tempBasal) {
+ return isReady()
+ .concatMapSingle(v -> TEMP_BASAL_SCHEDULE_START.start(tempBasal.getDurationMinutes(), tempBasal.getDoseUnitPerHour(), tempBasal.getPercent()))
+ .doOnNext(this::checkResponse)
+ .firstOrError()
+ .observeOn(aapsSchedulers.getIo())
+ .doOnSuccess(v -> onTempBasalStarted(tempBasal))
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StartTempBasalTask error"));
+ }
+
+ private void onTempBasalStarted(TempBasal tempBasal) {
+ pm.getTempBasalManager().updateBasalRunning(tempBasal);
+ pm.flushTempBasalManager();
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+ }
+
+ @Override
+ protected void preCondition() throws Exception {
+ checkPatchConnected();
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopBasalTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopBasalTask.java
new file mode 100644
index 0000000000..20ddbfff38
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopBasalTask.java
@@ -0,0 +1,124 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import android.os.SystemClock;
+
+import info.nightscout.androidaps.interfaces.PumpSync;
+import info.nightscout.androidaps.logging.UserEntryLogger;
+import info.nightscout.androidaps.utils.userEntry.UserEntryMapper;
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BasalStop;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BasalStopResponse;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.interfaces.CommandQueue;
+import info.nightscout.shared.logging.AAPSLogger;
+import info.nightscout.androidaps.queue.Callback;
+import info.nightscout.androidaps.queue.commands.Command;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.Single;
+import io.reactivex.rxjava3.subjects.BehaviorSubject;
+
+@Singleton
+public class StopBasalTask extends TaskBase {
+ @Inject IPreferenceManager pm;
+ @Inject CommandQueue commandQueue;
+ @Inject AAPSLogger aapsLogger;
+ @Inject PumpSync pumpSync;
+ @Inject UserEntryLogger uel;
+
+ private final BasalStop BASAL_STOP;
+ private final BehaviorSubject bolusCheckSubject = BehaviorSubject.create();
+ private final BehaviorSubject exbolusCheckSubject = BehaviorSubject.create();
+ private final BehaviorSubject basalCheckSubject = BehaviorSubject.create();
+
+ @Inject
+ public StopBasalTask() {
+ super(TaskFunc.STOP_BASAL);
+
+ BASAL_STOP = new BasalStop();
+ }
+
+ private Observable getBolusSebject(){
+ return bolusCheckSubject.hide();
+ }
+
+ private Observable getExbolusSebject(){
+ return exbolusCheckSubject.hide();
+ }
+
+ private Observable getBasalSebject(){
+ return basalCheckSubject.hide();
+ }
+
+ public Single stop() {
+
+ if (commandQueue.isRunning(Command.CommandType.BOLUS)) {
+ uel.log(UserEntryMapper.Action.CANCEL_BOLUS, UserEntryMapper.Sources.EOPatch2);
+ commandQueue.cancelAllBoluses(null);
+ SystemClock.sleep(650);
+ }
+ bolusCheckSubject.onNext(true);
+
+ if (pumpSync.expectedPumpState().getExtendedBolus() != null) {
+ uel.log(UserEntryMapper.Action.CANCEL_EXTENDED_BOLUS, UserEntryMapper.Sources.EOPatch2);
+ commandQueue.cancelExtended(new Callback() {
+ @Override
+ public void run() {
+ exbolusCheckSubject.onNext(true);
+ }
+ });
+ }else{
+ exbolusCheckSubject.onNext(true);
+ }
+
+ if (pumpSync.expectedPumpState().getTemporaryBasal() != null) {
+ uel.log(UserEntryMapper.Action.CANCEL_TEMP_BASAL, UserEntryMapper.Sources.EOPatch2);
+ commandQueue.cancelTempBasal(true, new Callback() {
+ @Override
+ public void run() {
+ basalCheckSubject.onNext(true);
+ }
+ });
+ }else{
+ basalCheckSubject.onNext(true);
+ }
+
+ return Observable.zip(getBolusSebject(), getExbolusSebject(), getBasalSebject(), (bolusReady, exbolusReady, basalReady)
+ -> (bolusReady && exbolusReady && basalReady))
+ .filter(ready -> ready)
+ .flatMap(v -> isReady())
+ .concatMapSingle(v -> BASAL_STOP.stop())
+ .doOnNext(this::checkResponse)
+ .firstOrError()
+ .doOnSuccess(this::onBasalStopped)
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StopBasalTask error"));
+ }
+
+ private void onBasalStopped(BasalStopResponse response) {
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+ }
+
+ public synchronized void enqueue() {
+ boolean ready = (disposable == null || disposable.isDisposed());
+
+ if (ready) {
+ disposable = stop()
+ .timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
+ .subscribe(v -> {
+ bolusCheckSubject.onNext(false);
+ exbolusCheckSubject.onNext(false);
+ basalCheckSubject.onNext(false);
+ });
+ }
+ }
+
+ @Override
+ protected void preCondition() throws Exception {
+ checkPatchConnected();
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopComboBolusTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopComboBolusTask.java
new file mode 100644
index 0000000000..2e5393a0eb
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopComboBolusTask.java
@@ -0,0 +1,84 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.define.IPatchConstant;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.code.PatchBleResultCode;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BolusStop;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusStopResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.ComboBolusStopResponse;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class StopComboBolusTask extends BolusTask {
+ private final BolusStop BOLUS_STOP;
+
+ @Inject
+ public StopComboBolusTask() {
+ super(TaskFunc.STOP_COMBO_BOLUS);
+ BOLUS_STOP = new BolusStop();
+ }
+
+ public Single stop() {
+ return isReady()
+ .concatMapSingle(v -> stopJob())
+ .firstOrError()
+ .doOnSuccess(this::checkResponse)
+ .doOnSuccess(this::onComboBolusStopped)
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StopComboBolusTask error"));
+ }
+
+ public Single stopJob() {
+ return Single.zip(
+ BOLUS_STOP.stop(IPatchConstant.EXT_BOLUS_ID),
+ BOLUS_STOP.stop(IPatchConstant.NOW_BOLUS_ID),
+ (ext, now) -> createStopComboBolusResponse(now, ext));
+ }
+
+ private ComboBolusStopResponse createStopComboBolusResponse(BolusStopResponse now, BolusStopResponse ext) {
+ int idNow = now.isSuccess() ? IPatchConstant.NOW_BOLUS_ID : 0;
+ int idExt = ext.isSuccess() ? IPatchConstant.EXT_BOLUS_ID : 0;
+
+ int injectedAmount = now.getInjectedBolusAmount();
+ int injectingAmount = now.getInjectingBolusAmount();
+
+ int injectedExAmount = ext.getInjectedBolusAmount();
+ int injectingExAmount = ext.getInjectingBolusAmount();
+
+ if (idNow == 0 && idExt == 0) {
+ return new ComboBolusStopResponse(IPatchConstant.NOW_BOLUS_ID, PatchBleResultCode.BOLUS_UNKNOWN_ID);
+ }
+
+ return new ComboBolusStopResponse(idNow, injectedAmount, injectingAmount, idExt, injectedExAmount, injectingExAmount);
+ }
+
+ private void onComboBolusStopped(ComboBolusStopResponse response) {
+ if (response.getId() != 0)
+ updateNowBolusStopped(response.getInjectedBolusAmount());
+
+ if (response.getExtId() != 0)
+ updateExtBolusStopped(response.getInjectedExBolusAmount());
+
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+ }
+
+ public synchronized void enqueue() {
+ boolean ready = (disposable == null || disposable.isDisposed());
+
+ if (ready) {
+ disposable = stop()
+ .timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
+ .subscribe();
+ }
+ }
+
+ @Override
+ protected void preCondition() throws Exception {
+ checkPatchConnected();
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopExtBolusTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopExtBolusTask.java
new file mode 100644
index 0000000000..079855197e
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopExtBolusTask.java
@@ -0,0 +1,57 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.define.IPatchConstant;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BolusStop;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusStopResponse;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class StopExtBolusTask extends BolusTask {
+ private final BolusStop BOLUS_STOP;
+
+ @Inject
+ public StopExtBolusTask() {
+ super(TaskFunc.STOP_EXT_BOLUS);
+ BOLUS_STOP = new BolusStop();
+ }
+
+ public Single stop() {
+ return isReady().concatMapSingle(v -> stopJob()).firstOrError()
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StopExtBolusTask error"));
+ }
+
+ public Single stopJob() {
+ return BOLUS_STOP.stop(IPatchConstant.EXT_BOLUS_ID)
+ .doOnSuccess(this::checkResponse)
+ .doOnSuccess(this::onExtBolusStopped);
+ }
+
+
+ private void onExtBolusStopped(BolusStopResponse response) {
+ updateExtBolusStopped(response.getInjectedBolusAmount());
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+ }
+
+ public synchronized void enqueue() {
+ boolean ready = (disposable == null || disposable.isDisposed());
+
+ if (ready) {
+ disposable = stop()
+ .timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
+ .subscribe();
+ }
+ }
+
+ @Override
+ protected void preCondition() throws Exception {
+ //checkPatchActivated();
+ checkPatchConnected();
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopNowBolusTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopNowBolusTask.java
new file mode 100644
index 0000000000..fdb6b9d0f6
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopNowBolusTask.java
@@ -0,0 +1,59 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BolusStop;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.define.IPatchConstant;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusStopResponse;
+import info.nightscout.androidaps.utils.rx.AapsSchedulers;
+import info.nightscout.shared.logging.LTag;
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class StopNowBolusTask extends BolusTask {
+ private final BolusStop BOLUS_STOP;
+
+ @Inject AapsSchedulers aapsSchedulers;
+
+ @Inject
+ public StopNowBolusTask() {
+ super(TaskFunc.STOP_NOW_BOLUS);
+ BOLUS_STOP = new BolusStop();
+ }
+
+ public Single stop() {
+ return isReady()
+ .observeOn(aapsSchedulers.getMain())
+ .concatMapSingle(v -> stopJob()).firstOrError()
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StopNowBolusTask error"));
+ }
+
+ public Single stopJob() {
+ return BOLUS_STOP.stop(IPatchConstant.NOW_BOLUS_ID)
+ .doOnSuccess(this::checkResponse)
+ .doOnSuccess(this::onNowBolusStopped);
+ }
+
+ private void onNowBolusStopped(BolusStopResponse response) {
+ updateNowBolusStopped(response.getInjectedBolusAmount());
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+ }
+
+ public synchronized void enqueue() {
+ boolean ready = (disposable == null || disposable.isDisposed());
+
+ if (ready) {
+ disposable = stop()
+ .timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
+ .subscribe();
+ }
+ }
+
+ @Override
+ protected void preCondition() throws Exception {
+ checkPatchConnected();
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopTempBasalTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopTempBasalTask.java
new file mode 100644
index 0000000000..dfed1f1105
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopTempBasalTask.java
@@ -0,0 +1,55 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.TempBasalScheduleStop;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class StopTempBasalTask extends TaskBase {
+ private final TempBasalScheduleStop TEMP_BASAL_SCHEDULE_STOP;
+
+ @Inject
+ public StopTempBasalTask() {
+ super(TaskFunc.STOP_TEMP_BASAL);
+
+ TEMP_BASAL_SCHEDULE_STOP = new TempBasalScheduleStop();
+ }
+
+ public Single stop() {
+ return isReady().concatMapSingle(v -> stopJob()).firstOrError()
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StopTempBasalTask error"));
+ }
+
+ public Single stopJob() {
+ return TEMP_BASAL_SCHEDULE_STOP.stop()
+ .doOnSuccess(this::checkResponse)
+ .doOnSuccess(v -> onTempBasalCanceled());
+ }
+
+ private void onTempBasalCanceled() {
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+ }
+
+ public synchronized void enqueue() {
+ boolean ready = (disposable == null || disposable.isDisposed());
+
+ if (ready) {
+ disposable = stop()
+ .timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
+ .subscribe();
+ }
+ }
+
+ @Override
+ protected void preCondition() throws Exception {
+ //checkPatchActivated();
+ checkPatchConnected();
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SyncBasalHistoryTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SyncBasalHistoryTask.java
new file mode 100644
index 0000000000..20265ec379
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SyncBasalHistoryTask.java
@@ -0,0 +1,139 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BasalHistoryGetExBig;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BasalHistoryIndexGet;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.TempBasalHistoryGetExBig;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BasalHistoryIndexResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BasalHistoryResponse;
+import io.reactivex.rxjava3.core.Single;
+
+import static info.nightscout.androidaps.plugins.pump.eopatch.core.define.IPatchConstant.BASAL_HISTORY_SIZE_BIG;
+
+@Singleton
+public class SyncBasalHistoryTask extends TaskBase {
+ @Inject IPreferenceManager pm;
+
+ private final BasalHistoryIndexGet BASAL_HISTORY_INDEX_GET;
+ private final BasalHistoryGetExBig BASAL_HISTORY_GET_EX_BIG;
+ private final TempBasalHistoryGetExBig TEMP_BASAL_HISTORY_GET_EX_BIG;
+
+ @Inject
+ public SyncBasalHistoryTask() {
+ super(TaskFunc.SYNC_BASAL_HISTORY);
+
+ BASAL_HISTORY_INDEX_GET = new BasalHistoryIndexGet();
+ BASAL_HISTORY_GET_EX_BIG = new BasalHistoryGetExBig();
+ TEMP_BASAL_HISTORY_GET_EX_BIG = new TempBasalHistoryGetExBig();
+ }
+
+ public Single sync(int end) {
+ return Single.just(1); // 베이젤 싱크 사용 안함
+ }
+
+ public Single sync() {
+ return Single.just(1); // 베이젤 싱크 사용 안함
+ }
+
+ private Single getLastIndex() {
+ return BASAL_HISTORY_INDEX_GET.get()
+ .doOnSuccess(this::checkResponse)
+ .map(BasalHistoryIndexResponse::getLastFinishedIndex);
+ }
+
+ private Single syncBoth(int start, int end) {
+ int count = end - start + 1;
+
+ if (count > 0) {
+ return Single.zip(
+ BASAL_HISTORY_GET_EX_BIG.get(start, count),
+ TEMP_BASAL_HISTORY_GET_EX_BIG.get(start, count),
+ (normal, temp) -> onBasalHistoryResponse(normal, temp, start, end));
+ } else {
+ return Single.just(-1);
+ }
+ }
+
+ public synchronized void enqueue(int end) {
+
+ boolean ready = (disposable == null || disposable.isDisposed());
+
+ if (ready) {
+ disposable = sync(end)
+ .timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
+ .subscribe();
+ }
+ }
+
+ public synchronized void enqueue() {
+ boolean ready = (disposable == null || disposable.isDisposed());
+
+ if (ready) {
+ disposable = sync()
+ .timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
+ .subscribe();
+ }
+ }
+
+ private int onBasalHistoryResponse(BasalHistoryResponse n, BasalHistoryResponse t,
+ int startRequested, int end) {
+
+ if (!n.isSuccess() || !t.isSuccess() || n.getSeq() != t.getSeq()) {
+ return -1;
+ }
+
+ int start = n.getSeq();
+
+ float[] normal = n.getInjectedDoseValues();
+ float[] temp = t.getInjectedDoseValues();
+
+ int count = Math.min(end - start + 1, BASAL_HISTORY_SIZE_BIG);
+ count = Math.min(count, normal.length);
+ count = Math.min(count, temp.length);
+
+ return updateInjected(normal, temp, start, end);
+ }
+
+ public synchronized int updateInjected(float[] normal, float[] temp, int start, int end) {
+ if (pm.getPatchState().isPatchInternalSuspended() && pm.getPatchConfig().isInBasalPausedTime() == false) {
+ return -1;
+ }
+
+ int lastUpdatedIndex = -1;
+ int count = end - start + 1;
+
+ if (count > normal.length) {
+ count = normal.length;
+ }
+
+ if (count > 0) {
+ int lastSyncIndex = pm.getPatchConfig().getLastIndex();
+ for (int i = 0;i < count;i++) {
+ int seq = start + i;
+ if (seq < lastSyncIndex)
+ continue;
+
+ if (start <= seq && seq <= end) {
+ lastUpdatedIndex = seq;
+ }
+ }
+ }
+
+ return lastUpdatedIndex;
+ }
+
+ private void updatePatchLastIndex(int newIndex) {
+ int lastIndex = pm.getPatchConfig().getLastIndex();
+
+ if (lastIndex < newIndex) {
+ pm.getPatchConfig().setLastIndex(newIndex);
+ pm.flushPatchConfig();
+ }
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/TaskBase.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/TaskBase.java
new file mode 100644
index 0000000000..8e0e0d89d1
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/TaskBase.java
@@ -0,0 +1,98 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.BleConnectionState;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.IBleDevice;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.Patch;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.exception.NoActivatedPatchException;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.exception.PatchDisconnectedException;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BaseResponse;
+
+import java.util.HashMap;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.shared.logging.AAPSLogger;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.disposables.Disposable;
+
+@Singleton
+public class TaskBase {
+ protected IBleDevice patch;
+
+ @Inject AAPSLogger aapsLogger;
+ @Inject protected IPreferenceManager pm;
+ @Inject TaskQueue taskQueue;
+
+ TaskFunc func;
+
+ static HashMap maps = new HashMap<>();
+
+ /* enqueue 시 사용 */
+ protected Disposable disposable;
+
+ protected final Object lock = new Object();
+
+ protected static final long TASK_ENQUEUE_TIME_OUT = 60; // SECONDS
+
+ @Inject
+ public TaskBase(TaskFunc func) {
+ this.func = func;
+ maps.put(func, this);
+ patch = Patch.getInstance();
+ }
+
+ /* Task 들의 작업 순서 및 조건 체크 */
+ protected Observable isReady() {
+ return taskQueue.isReady(func).doOnNext(v -> preCondition());
+ }
+
+ protected Observable isReady2() {
+ return taskQueue.isReady2(func).doOnNext(v -> preCondition());
+ }
+
+ protected void checkResponse(BaseResponse response) throws Exception {
+ if (!response.isSuccess()) {
+ throw new Exception("Response failed! - "+response.resultCode.name());
+ }
+ }
+
+ public static void enqueue(TaskFunc func) {
+ TaskBase task = maps.get(func);
+
+ if (task != null) {
+ task.enqueue();
+ }
+ }
+
+ public static void enqueue(TaskFunc func, Boolean flag) {
+ TaskBase task = maps.get(func);
+
+ if (task != null) {
+ task.enqueue(flag);
+ }
+ }
+
+ protected synchronized void enqueue() {
+ }
+
+ protected synchronized void enqueue(Boolean flag) {
+ }
+
+ protected void preCondition() throws Exception {
+
+ }
+
+ protected void checkPatchConnected() throws Exception {
+ if (patch.getConnectionState() == BleConnectionState.DISCONNECTED) {
+ throw new PatchDisconnectedException();
+ }
+ }
+
+ protected void checkPatchActivated() throws Exception {
+ if (pm.getPatchConfig().isDeactivated()) {
+ throw new NoActivatedPatchException();
+ }
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/TaskFunc.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/TaskFunc.java
new file mode 100644
index 0000000000..2e1f8f77b8
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/TaskFunc.java
@@ -0,0 +1,31 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+public enum TaskFunc {
+ START_BOND,
+ GET_PATCH_INFO,
+ SELF_TEST,
+ PRIMING,
+ NEEDLE_SENSING,
+ ACTIVATE,
+ DEACTIVATE,
+ UPDATE_CONNECTION,
+ START_NORMAL_BASAL,
+ START_TEMP_BASAL,
+ STOP_TEMP_BASAL,
+ RESUME_BASAL,
+ PAUSE_BASAL,
+ STOP_BASAL,
+ STOP_NOW_BOLUS,
+ STOP_EXT_BOLUS,
+ STOP_COMBO_BOLUS,
+ START_QUICK_BOLUS,
+ START_CALC_BOLUS,
+ SYNC_BASAL_HISTORY,
+ READ_BOLUS_FINISH_TIME,
+ READ_TEMP_BASAL_FINISH_TIME,
+ FETCH_ALARM,
+ LOW_RESERVOIR,
+ SET_GLOBAL_TIME,
+ INFO_REMINDER,
+ INTERNAL_SUSPEND
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/TaskQueue.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/TaskQueue.java
new file mode 100644
index 0000000000..c88f4f6a80
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/TaskQueue.java
@@ -0,0 +1,114 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Queue;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.utils.rx.AapsSchedulers;
+import info.nightscout.shared.logging.AAPSLogger;
+import info.nightscout.shared.logging.LTag;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.subjects.BehaviorSubject;
+
+@Singleton
+public class TaskQueue {
+ @Inject AAPSLogger aapsLogger;
+ @Inject AapsSchedulers aapsSchedulers;
+
+ Queue queue = new LinkedList<>();
+
+ private int sequence = 0;
+ private final BehaviorSubject ticketSubject = BehaviorSubject.create();
+ private final BehaviorSubject sizeSubject = BehaviorSubject.createDefault(0);
+
+ @Inject
+ public TaskQueue() {
+ }
+
+ protected Observable observeQueue() {
+ return sizeSubject.distinctUntilChanged();
+ }
+
+ protected synchronized Observable isReady(final TaskFunc function) {
+ return Observable.fromCallable(() -> publishTicket(function))
+ .concatMap(v -> ticketSubject
+ .takeUntil(it -> it.number > v)
+ .filter(it -> it.number == v))
+ .doOnNext(v -> aapsLogger.debug(LTag.PUMPCOMM, String.format("Task #:%s started func:%s", v.number, v.func.name())))
+ .observeOn(aapsSchedulers.getIo())
+ .map(it -> it.func)
+ .doFinally(this::done);
+ }
+
+ protected synchronized Observable isReady2(final TaskFunc function) {
+ return observeQueue()
+ .filter(size -> size == 0).concatMap(v -> isReady(function));
+ }
+
+ private synchronized int publishTicket(final TaskFunc function) {
+ int turn = sequence++;
+ aapsLogger.debug(LTag.PUMPCOMM, String.format("publishTicket() Task #:%s is assigned func:%s", turn, function.name()));
+
+ PatchTask task = new PatchTask(turn, function);
+ addQueue(task);
+ return turn;
+ }
+
+ private synchronized void addQueue(PatchTask task) {
+ queue.add(task);
+ int size = queue.size();
+ sizeSubject.onNext(size);
+
+ if (size == 1) {
+ ticketSubject.onNext(task);
+ }
+ }
+
+ private synchronized void done() {
+ if (queue.size() > 0) {
+ PatchTask done = queue.remove();
+ aapsLogger.debug(LTag.PUMPCOMM, String.format("done() Task #:%s completed func:%s task remaining:%s",
+ done.number, done.func.name(), queue.size()));
+ }
+
+ int size = queue.size();
+ sizeSubject.onNext(size);
+
+ PatchTask next = queue.peek();
+ if (next != null) {
+ ticketSubject.onNext(next);
+ }
+ }
+
+ public synchronized boolean has(TaskFunc func) {
+ if (queue.size() > 1) {
+ Iterator iterator = queue.iterator();
+
+ /* remove 1st queue */
+ iterator.next();
+
+ while (iterator.hasNext()) {
+ PatchTask item = iterator.next();
+ if (item.func == func) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ static class PatchTask {
+
+ int number;
+ TaskFunc func;
+
+ PatchTask(int number, TaskFunc func) {
+ this.number = number;
+ this.func = func;
+ }
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/UpdateConnectionTask.java b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/UpdateConnectionTask.java
new file mode 100644
index 0000000000..311154a388
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/UpdateConnectionTask.java
@@ -0,0 +1,55 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.PatchStateManager;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.UpdateConnection;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.UpdateConnectionResponse;
+import io.reactivex.rxjava3.core.Single;
+
+@Singleton
+public class UpdateConnectionTask extends TaskBase {
+ @Inject PatchStateManager patchStateManager;
+
+ private UpdateConnection UPDATE_CONNECTION;
+
+ @Inject
+ public UpdateConnectionTask() {
+ super(TaskFunc.UPDATE_CONNECTION);
+
+ UPDATE_CONNECTION = new UpdateConnection();
+ }
+
+ public Single update() {
+ return isReady().concatMapSingle(v -> updateJob()).firstOrError();
+ }
+
+ public Single updateJob() {
+ return UPDATE_CONNECTION.get()
+ .doOnSuccess(this::checkResponse)
+ .map(UpdateConnectionResponse::getPatchState)
+ .map(bytes -> PatchState.Companion.create(bytes, System.currentTimeMillis()))
+ .doOnSuccess(state -> onUpdateConnection(state))
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "UpdateConnectionTask error"));
+ }
+
+ private void onUpdateConnection(PatchState patchState) {
+ patchStateManager.updatePatchState(patchState);
+ }
+
+ public synchronized void enqueue() {
+ boolean ready = (disposable == null || disposable.isDisposed());
+
+ if (ready) {
+ disposable = update()
+ .timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
+ .subscribe();
+ }
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/AlarmCategory.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/AlarmCategory.kt
new file mode 100644
index 0000000000..93f00b95ee
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/AlarmCategory.kt
@@ -0,0 +1,7 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+enum class AlarmCategory{
+ NONE,
+ ALARM,
+ ALERT;
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/BasalStatus.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/BasalStatus.kt
new file mode 100644
index 0000000000..6758b29836
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/BasalStatus.kt
@@ -0,0 +1,34 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+enum class BasalStatus constructor(val rawValue: Int) {
+ STOPPED(0),
+ PAUSED(1), //템프베이젤 주입중
+ SUSPENDED(2), //주입 정지
+ STARTED(3), //주입중
+ SELECTED(4); //패치 폐기
+
+ val isStarted: Boolean
+ get() = this == STARTED
+
+ val isSuspended: Boolean
+ get() = this == SUSPENDED
+
+ val isStopped: Boolean
+ get() = this == STOPPED
+
+ companion object {
+ @JvmStatic
+ fun ofRaw(rawValue: Int?): BasalStatus {
+ if (rawValue == null) {
+ return STOPPED
+ }
+
+ for (t in values()) {
+ if (t.rawValue == rawValue) {
+ return t
+ }
+ }
+ return STOPPED
+ }
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/BolusExDuration.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/BolusExDuration.kt
new file mode 100644
index 0000000000..fbb1a87e37
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/BolusExDuration.kt
@@ -0,0 +1,40 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+import java.util.concurrent.TimeUnit
+
+enum class BolusExDuration constructor(val index: Int, val minute: Int, val hour: Float) {
+ OFF(0, 0, 0f),
+ MINUTE_30(1, 30, 0.5f),
+ MINUTE_60(2, 60, 1.0f),
+ MINUTE_90(3, 90, 1.5f),
+ MINUTE_120(4, 120, 2.0f),
+ MINUTE_150(5, 150, 2.5f),
+ MINUTE_180(6, 180, 3.0f),
+ MINUTE_210(7, 210, 3.5f),
+ MINUTE_240(8, 240, 4.0f),
+ MINUTE_270(9, 270, 4.5f),
+ MINUTE_300(10, 300, 5.0f),
+ MINUTE_330(11, 330, 5.5f),
+ MINUTE_360(12, 360, 6.0f),
+ MINUTE_390(13, 390, 6.5f),
+ MINUTE_420(14, 420, 7.0f),
+ MINUTE_450(15, 450, 7.5f),
+ MINUTE_480(16, 480, 8.0f);
+
+ fun milli(): Long {
+ return TimeUnit.MINUTES.toMillis(this.minute.toLong())
+ }
+
+ companion object {
+ @JvmStatic
+ fun ofRaw(rawValue: Int): BolusExDuration {
+ for (t in values()) {
+ if (t.minute == rawValue) {
+ return t
+ }
+ }
+ return OFF
+ }
+ }
+
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/DeactivationStatus.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/DeactivationStatus.kt
new file mode 100644
index 0000000000..829b865865
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/DeactivationStatus.kt
@@ -0,0 +1,22 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+
+enum class DeactivationStatus {
+ DEACTIVATION_FAILED,
+ NORMAL_DEACTIVATED,
+ FORCE_DEACTIVATED;
+
+ val isDeactivated: Boolean
+ get() = this == NORMAL_DEACTIVATED || this == FORCE_DEACTIVATED
+
+ companion object {
+ @JvmStatic
+ fun of(isSuccess: Boolean, forced: Boolean): DeactivationStatus {
+ return when {
+ isSuccess -> NORMAL_DEACTIVATED
+ forced -> FORCE_DEACTIVATED
+ else -> DEACTIVATION_FAILED
+ }
+ }
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/EventType.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/EventType.kt
new file mode 100644
index 0000000000..07ddfb4e0f
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/EventType.kt
@@ -0,0 +1,22 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+enum class EventType {
+ ACTIVATION_CLICKED,
+ DEACTIVATION_CLICKED,
+ SUSPEND_CLICKED,
+ RESUME_CLICKED,
+ INVALID_BASAL_RATE,
+ PROFILE_NOT_SET,
+ SHOW_PATCH_COMM_DIALOG,
+ DISMISS_PATCH_COMM_DIALOG,
+ SHOW_PATCH_COMM_ERROR_DIALOG,
+ SHOW_BONDED_DIALOG,
+ SHOW_CHANGE_PATCH_DIALOG,
+ FINISH_ACTIVITY,
+ SHOW_DISCARD_DIALOG,
+ PAUSE_BASAL_SUCCESS,
+ PAUSE_BASAL_FAILED,
+ RESUME_BASAL_SUCCESS,
+ RESUME_BASAL_FAILED
+ ;
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchExpireAlertTime.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchExpireAlertTime.kt
new file mode 100644
index 0000000000..d34c568c04
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchExpireAlertTime.kt
@@ -0,0 +1,28 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+enum class PatchExpireAlertTime constructor(val index: Int, val hour: Int) {
+ HOUR_1(0, 1),
+ HOUR_2(1, 2),
+ HOUR_3(2, 3),
+ HOUR_4(3, 4),
+ HOUR_5(4, 5),
+ HOUR_6(5, 6),
+ HOUR_7(6, 7),
+ HOUR_8(7, 8),
+ HOUR_9(8, 9),
+ HOUR_10(9, 10),
+ HOUR_11(10, 11),
+ HOUR_12(11, 12),
+ HOUR_13(12, 13),
+ HOUR_14(13, 14),
+ HOUR_15(14, 15),
+ HOUR_16(15, 16),
+ HOUR_17(16, 17),
+ HOUR_18(17, 18),
+ HOUR_19(18, 19),
+ HOUR_20(19, 20),
+ HOUR_21(20, 21),
+ HOUR_22(21, 22),
+ HOUR_23(22, 23),
+ HOUR_24(23, 24);
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchLifecycle.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchLifecycle.kt
new file mode 100644
index 0000000000..db153c6130
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchLifecycle.kt
@@ -0,0 +1,19 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+enum class PatchLifecycle constructor(val rawValue: Int) {
+ SHUTDOWN(1),
+ BONDED(2),
+ SAFETY_CHECK(3),
+ REMOVE_NEEDLE_CAP(4),
+ REMOVE_PROTECTION_TAPE(5),
+ ROTATE_KNOB(6),
+ BASAL_SETTING(7),
+ ACTIVATED(8);
+
+ val isShutdown: Boolean
+ get() = this == SHUTDOWN
+
+ val isActivated: Boolean
+ get() = this == ACTIVATED
+
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchStep.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchStep.kt
new file mode 100644
index 0000000000..02ca419279
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchStep.kt
@@ -0,0 +1,29 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+enum class PatchStep {
+ SAFE_DEACTIVATION,
+ MANUALLY_TURNING_OFF_ALARM,
+ DISCARDED,
+ DISCARDED_FOR_CHANGE,
+ DISCARDED_FROM_ALARM,
+ WAKE_UP,
+ CONNECT_NEW,
+ REMOVE_NEEDLE_CAP,
+ REMOVE_PROTECTION_TAPE,
+ SAFETY_CHECK,
+ ROTATE_KNOB,
+ ROTATE_KNOB_NEEDLE_INSERTION_ERROR,
+ BASAL_SCHEDULE,
+ SETTING_REMINDER_TIME,
+ CHECK_CONNECTION,
+ CANCEL,
+ COMPLETE,
+ BACK_TO_HOME,
+ FINISH;
+
+ val isSafeDeactivation: Boolean
+ get() = this == SAFE_DEACTIVATION
+
+ val isCheckConnection: Boolean
+ get() = this == CHECK_CONNECTION
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/SettingKeys.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/SettingKeys.kt
new file mode 100644
index 0000000000..23eb459cb8
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/SettingKeys.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+
+class SettingKeys {
+ companion object{
+ val LOW_RESERVOIR_REMINDERS: Int = R.string.key_eopatch_low_reservoir_reminders
+ val EXPIRATION_REMINDERS: Int = R.string.key_eopatch_expiration_reminders
+ val BUZZER_REMINDERS: Int = R.string.key_eopatch_patch_buzzer_reminders
+
+ val PATCH_CONFIG: Int = R.string.key_eopatch_patch_config
+ val PATCH_STATE: Int = R.string.key_eopatch_patch_state
+ val BOLUS_CURRENT: Int = R.string.key_eopatch_bolus_current
+ val NORMAL_BASAL: Int = R.string.key_eopatch_normal_basal
+ val TEMP_BASAL: Int = R.string.key_eopatch_temp_basal
+ val ALARMS: Int = R.string.key_eopatch_bolus_current
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/UnitOrPercent.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/UnitOrPercent.kt
new file mode 100644
index 0000000000..1c7aa5795d
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/UnitOrPercent.kt
@@ -0,0 +1,8 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+enum class UnitOrPercent {
+ P,
+ U;
+
+ fun isPercentage() = this == P
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchInjectHelpers.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchInjectHelpers.kt
new file mode 100644
index 0000000000..d749761e66
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchInjectHelpers.kt
@@ -0,0 +1,12 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.dagger
+
+import javax.inject.Qualifier
+import javax.inject.Scope
+
+@Qualifier
+annotation class EopatchPluginQualifier
+
+@MustBeDocumented
+@Scope
+@Retention(AnnotationRetention.RUNTIME)
+annotation class FragmentScope
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchModule.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchModule.kt
new file mode 100644
index 0000000000..e148cf76ae
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchModule.kt
@@ -0,0 +1,139 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.dagger
+
+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.androidaps.plugins.pump.eopatch.OsAlarmReceiver
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmManager
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmRegistry
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmManager
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmRegistry
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPatchManager
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.PatchManager
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.PreferenceManager
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.*
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs.AlarmDialog
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs.ActivationNotCompleteDialog
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs.CommonDialog
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchOverviewViewModel
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchViewModel
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.ViewModelFactory
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.ViewModelKey
+import javax.inject.Provider
+import javax.inject.Singleton
+
+@Module(includes = [EopatchPrefModule::class])
+@Suppress("unused")
+abstract class EopatchModule {
+ companion object {
+ @Provides
+ @EopatchPluginQualifier
+ fun providesViewModelFactory(@EopatchPluginQualifier viewModels: MutableMap, @JvmSuppressWildcards Provider>): ViewModelProvider.Factory {
+ return ViewModelFactory(viewModels)
+ }
+
+ }
+
+ @Binds
+ @Singleton
+ abstract fun bindPatchManager(patchManager: PatchManager): IPatchManager
+
+ @Binds
+ @Singleton
+ abstract fun bindAlarmManager(alarmManager: AlarmManager): IAlarmManager
+
+ @Binds
+ @Singleton
+ abstract fun bindAlarmRegistry(alarmRegistry: AlarmRegistry): IAlarmRegistry
+
+ @Binds
+ @Singleton
+ abstract fun bindPreferenceManager(preferenceManager: PreferenceManager): IPreferenceManager
+
+ // #### VIEW MODELS ############################################################################
+ @Binds
+ @IntoMap
+ @EopatchPluginQualifier
+ @ViewModelKey(EopatchOverviewViewModel::class)
+ internal abstract fun bindsEopatchOverviewViewmodel(viewModel: EopatchOverviewViewModel): ViewModel
+
+ @Binds
+ @IntoMap
+ @EopatchPluginQualifier
+ @ViewModelKey(EopatchViewModel::class)
+ internal abstract fun bindsEopatchViewModel(viewModel: EopatchViewModel): ViewModel
+
+ // #### FRAGMENTS ##############################################################################
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesEopatchOverviewFragment(): EopatchOverviewFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesEopatchSafeDeactivationFragment(): EopatchSafeDeactivationFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesEopatchTurningOffAlarmFragment(): EopatchTurningOffAlarmFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesEopatchRemoveFragment(): EopatchRemoveFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesEopatchWakeUpFragment(): EopatchWakeUpFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesEopatchConnectNewFragment(): EopatchConnectNewFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesEopatchRemoveNeedleCapFragment(): EopatchRemoveNeedleCapFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesEopatchRemoveProtectionTapeFragment(): EopatchRemoveProtectionTapeFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesEopatchSafetyCheckFragment(): EopatchSafetyCheckFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesEopatchRotateKnobFragment(): EopatchRotateKnobFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesEopatchBasalScheduleFragment(): EopatchBasalScheduleFragment
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesAlarmDialog(): AlarmDialog
+
+ @FragmentScope
+ @ContributesAndroidInjector
+ internal abstract fun contributesCommonDialog(): ActivationNotCompleteDialog
+
+ // Activities
+ @ContributesAndroidInjector
+ abstract fun contributesEopatchActivity(): EopatchActivity
+
+ @ContributesAndroidInjector
+ abstract fun contributesAlarmHelperActivity(): AlarmHelperActivity
+
+ @ContributesAndroidInjector
+ abstract fun contributesDialogHelperActivity(): DialogHelperActivity
+
+ @ContributesAndroidInjector
+ abstract fun contributesEoDialog(): CommonDialog
+
+ @ContributesAndroidInjector
+ abstract fun contributesOsAlarmReceiver(): OsAlarmReceiver
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchPrefModule.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchPrefModule.kt
new file mode 100644
index 0000000000..03cd624009
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchPrefModule.kt
@@ -0,0 +1,38 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.dagger
+
+import dagger.Module
+import dagger.Provides
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.Alarms
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.NormalBasalManager
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchConfig
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasalManager
+import javax.inject.Singleton
+
+@Module
+class EopatchPrefModule {
+ @Provides
+ @Singleton
+ internal fun providePatchConfig(): PatchConfig {
+ return PatchConfig()
+ }
+
+ @Provides
+ @Singleton
+ internal fun provideNormalBasalManager(): NormalBasalManager {
+ return NormalBasalManager()
+ }
+
+ @Provides
+ @Singleton
+ internal fun provideTempBasalManager(): TempBasalManager {
+ return TempBasalManager()
+ }
+
+ @Provides
+ @Singleton
+ internal fun provideAlarms(): Alarms {
+ return Alarms()
+ }
+}
+
+
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/event/EoPatchEvents.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/event/EoPatchEvents.kt
new file mode 100644
index 0000000000..409a4e6561
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/event/EoPatchEvents.kt
@@ -0,0 +1,11 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.event
+
+import androidx.annotation.StringRes
+import androidx.fragment.app.DialogFragment
+import info.nightscout.androidaps.events.Event
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode
+
+class EventEoPatchAlarm(var alarmCodes: Set, var isFirst: Boolean = false) : Event()
+class EventDialog(val dialog: DialogFragment, val show: Boolean) : Event()
+class EventProgressDialog(val show: Boolean, @StringRes val resId: Int = 0) : Event()
+class EventPatchActivationNotComplete : Event()
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/AppCompatActivityExtension.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/AppCompatActivityExtension.kt
new file mode 100644
index 0000000000..e6ad855b07
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/AppCompatActivityExtension.kt
@@ -0,0 +1,19 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.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()
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/BooleanExtension.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/BooleanExtension.kt
new file mode 100644
index 0000000000..1b560c6e0a
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/BooleanExtension.kt
@@ -0,0 +1,5 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.extension
+
+fun Boolean.takeOne(whenTrue: T, whenFalse: T): T {
+ return if(this) whenTrue else whenFalse
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/CharSequenceExtesnsion.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/CharSequenceExtesnsion.kt
new file mode 100644
index 0000000000..5763bf815d
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/CharSequenceExtesnsion.kt
@@ -0,0 +1,37 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.extension
+
+import android.text.Spanned
+
+fun CharSequence?.check(oldText: CharSequence?): Boolean {
+ val text = this
+ if (text === oldText || text == null && oldText?.length == 0) {
+ return false
+ }
+ if (text is Spanned) {
+ if (text == oldText) {
+ return false // No change in the spans, so don't set anything.
+ }
+ } else if (!text.haveContentsChanged(oldText)) {
+ return false // No content changes, so don't set anything.
+ }
+ return true
+}
+
+fun CharSequence?.haveContentsChanged(str2: CharSequence?): Boolean {
+ val str1: CharSequence? = this
+ if (str1 == null != (str2 == null)) {
+ return true
+ } else if (str1 == null) {
+ return false
+ }
+ val length = str1.length
+ if (length != str2!!.length) {
+ return true
+ }
+ for (i in 0 until length) {
+ if (str1[i] != str2[i]) {
+ return true
+ }
+ }
+ return false
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/FloatExtension.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/FloatExtension.kt
new file mode 100644
index 0000000000..304ae4a849
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/FloatExtension.kt
@@ -0,0 +1,16 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.extension
+
+import kotlin.math.abs
+
+fun Double.nearlyEqual(b: Double, epsilon: Double): Boolean {
+ val absA = abs(this)
+ val absB = abs(b)
+ val diff = abs(this - b)
+ return if (this == b) {
+ true
+ } else if (this == 0.0 || b == 0.0 || absA + absB < java.lang.Float.MIN_NORMAL) {
+ diff < epsilon * java.lang.Double.MIN_NORMAL
+ } else {
+ diff / (absA + absB).coerceAtMost(Double.MAX_VALUE) < epsilon
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/LongExtension.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/LongExtension.kt
new file mode 100644
index 0000000000..7fef444e07
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/LongExtension.kt
@@ -0,0 +1,15 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.extension
+
+import java.util.*
+import java.util.concurrent.TimeUnit
+
+internal val Long.date: Date
+ get() = Calendar.getInstance().also { it.timeInMillis = this }.time
+
+fun Long.getDiffDays(isRelative: Boolean = false): Long {
+ val inputTimeMillis = this
+ val currentTimeMillis = System.currentTimeMillis()
+ val diffTimeMillis = if (inputTimeMillis > currentTimeMillis) inputTimeMillis - currentTimeMillis else isRelative.takeOne(currentTimeMillis - inputTimeMillis, 0)
+
+ return TimeUnit.MILLISECONDS.toDays(diffTimeMillis)
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/ObservableExtension.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/ObservableExtension.kt
new file mode 100644
index 0000000000..1bc6009797
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/ObservableExtension.kt
@@ -0,0 +1,32 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.extension
+
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.disposables.Disposable
+import io.reactivex.rxjava3.schedulers.Schedulers
+
+fun Observable.observeOnMainThread(): Observable = observeOn(AndroidSchedulers.mainThread())
+
+fun Observable.observeOnComputation(): Observable = observeOn(Schedulers.computation())
+
+fun Observable.observeOnIo(): Observable = observeOn(Schedulers.io())
+
+fun Observable.subscribeEmpty(): Disposable = subscribe({}, {}, {})
+
+fun Observable.subscribeEmpty(onSuccess: (T) -> Unit): Disposable = subscribe(onSuccess, {}, {})
+
+fun Observable.subscribeEmpty(onSuccess: (T) -> Unit, onError: (Throwable) -> Unit): Disposable = subscribe(onSuccess, onError, {})
+
+fun Observable.subscribeDefault(aapsLogger: AAPSLogger): Disposable = subscribe({ aapsLogger.debug(LTag.PUMP, "onSuccess") }, { aapsLogger.error(LTag.PUMP, "onError", it) }, {
+ aapsLogger.debug(LTag.PUMP, "onComplete")
+})
+
+fun Observable.subscribeDefault(aapsLogger: AAPSLogger, onSuccess: (T) -> Unit): Disposable =
+ subscribe(onSuccess, { aapsLogger.error(LTag.PUMP, "onError", it) }, { aapsLogger.debug(LTag.PUMP, "onComplete") })
+
+fun Observable.subscribeDefault(aapsLogger: AAPSLogger, onSuccess: (T) -> Unit, onError: (Throwable) -> Unit): Disposable =
+ subscribe(onSuccess, onError, { aapsLogger.debug(LTag.PUMP, "onComplete") })
+
+fun Observable.with(): Observable = subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/SharedPreferencesExtension.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/SharedPreferencesExtension.kt
new file mode 100644
index 0000000000..ff08b717cb
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/SharedPreferencesExtension.kt
@@ -0,0 +1,40 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.extension
+
+import android.content.SharedPreferences
+import androidx.core.content.edit
+
+/**
+ * puts a key value pair in shared prefs if doesn't exists, otherwise updates value on given [key]
+ */
+operator fun SharedPreferences.set(key: String, commit: Boolean = false, value: Any?) {
+ when (value) {
+ is String? -> edit(commit) { putString(key, value) }
+ is Int -> edit(commit) { putInt(key, value) }
+ is Long -> edit(commit) { putLong(key, value) }
+ is Float -> edit(commit) { putFloat(key, value) }
+ is Boolean -> edit(commit) { putBoolean(key, value) }
+ else -> throw UnsupportedOperationException("Not yet implemented")
+ }
+}
+
+/**
+ * finds value on given key.
+ * [T] is the type of value
+ * @param defaultValue optional default value - will take null for strings, false for bool and -1 for numeric values if [defaultValue] is not specified
+ */
+inline operator fun SharedPreferences.get(key: String, defaultValue: T? = null): T? {
+ return when (T::class) {
+ String::class -> getString(key, defaultValue as? String) as T?
+ Int::class -> getInt(key, defaultValue as? Int ?: -1) as T?
+ Long::class -> getLong(key, defaultValue as? Long ?: -1) as T?
+ Float::class -> getFloat(key, defaultValue as? Float ?: -1f) as T?
+ Boolean::class -> getBoolean(key, defaultValue as? Boolean ?: false) as T?
+ else -> throw UnsupportedOperationException("Not yet implemented")
+ }
+}
+
+fun SharedPreferences.getString(key: String): String? = this[key]
+fun SharedPreferences.getInt(key: String): Int? = this[key]
+fun SharedPreferences.getFloat(key: String): Float? = this[key]
+fun SharedPreferences.getLong(key: String): Long? = this[key]
+fun SharedPreferences.getBoolean(key: String): Boolean? = this[key]
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/SingleExtension.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/SingleExtension.kt
new file mode 100644
index 0000000000..fa028a7b7a
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/SingleExtension.kt
@@ -0,0 +1,14 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.extension
+
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.disposables.Disposable
+import io.reactivex.rxjava3.schedulers.Schedulers
+
+fun Single.subscribeDefault(aapsLogger: AAPSLogger, onSuccess: (T) -> Unit): Disposable = subscribe(onSuccess) {
+ aapsLogger.error(LTag.PUMP, "onError", it)
+}
+
+fun Single.with(): Single = subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/StringExtension.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/StringExtension.kt
new file mode 100644
index 0000000000..55fc573532
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/StringExtension.kt
@@ -0,0 +1,22 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.extension
+
+import android.text.Html
+import org.json.JSONArray
+import org.json.JSONException
+import org.json.JSONObject
+import java.text.SimpleDateFormat
+import java.util.*
+
+@Throws(JSONException::class)
+fun String.toJson(): String =
+ when (get(0)) {
+ '{' -> JSONObject(this).toString(4)
+ '[' -> JSONArray(this).toString(4)
+ else -> ""
+ }
+
+fun String.fromHtml(): CharSequence = Html.fromHtml(this, Html.FROM_HTML_MODE_COMPACT)
+
+fun String.isEmpty(): Boolean{
+ return this.length == 0
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/ViewExtension.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/ViewExtension.kt
new file mode 100644
index 0000000000..041ce8e2f6
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/ViewExtension.kt
@@ -0,0 +1,34 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/AlarmHelperActivity.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/AlarmHelperActivity.kt
new file mode 100644
index 0000000000..f9fb16935b
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/AlarmHelperActivity.kt
@@ -0,0 +1,79 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui
+
+import android.os.Bundle
+import androidx.appcompat.app.AlertDialog
+import info.nightscout.androidaps.activities.DialogAppCompatActivity
+import info.nightscout.androidaps.core.R
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode
+import info.nightscout.androidaps.plugins.pump.eopatch.event.EventDialog
+import info.nightscout.androidaps.plugins.pump.eopatch.event.EventProgressDialog
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs.AlarmDialog
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs.ProgressDialogHelper
+import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.sharedPreferences.SP
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import javax.inject.Inject
+
+class AlarmHelperActivity : DialogAppCompatActivity() {
+ @Inject lateinit var sp : SP
+ @Inject lateinit var rxBus: RxBus
+ @Inject lateinit var aapsSchedulers: AapsSchedulers
+
+ private var disposable: CompositeDisposable = CompositeDisposable()
+ private var mProgressDialog: AlertDialog? = null
+
+ @Override
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setTheme(R.style.AppTheme_NoActionBar)
+
+ val alarmDialog = AlarmDialog()
+ alarmDialog.helperActivity = this
+ intent.getStringExtra("code")?.let{
+ alarmDialog.code = it
+ alarmDialog.alarmCode = AlarmCode.fromStringToCode(it)
+ }
+
+ alarmDialog.status = intent.getStringExtra("status")?:""
+ alarmDialog.sound = intent.getIntExtra("soundid", R.raw.error)
+ alarmDialog.title = intent.getStringExtra("title")?:""
+ if(alarmDialog.code != null)
+ alarmDialog.show(supportFragmentManager, "Alarm")
+
+ disposable.add(rxBus
+ .toObservable(EventProgressDialog::class.java)
+ .observeOn(aapsSchedulers.main)
+ .subscribe({
+ if(it.show){
+ showProgressDialog(it.resId)
+ }else{
+ dismissProgressDialog()
+ }
+ }, { })
+ )
+
+ disposable.add(rxBus
+ .toObservable(EventDialog::class.java)
+ .observeOn(aapsSchedulers.main)
+ .subscribe({
+ if(it.show) it.dialog.show(supportFragmentManager, "")
+ }, { })
+ )
+ }
+
+
+ private fun showProgressDialog(resId: Int){
+ if (mProgressDialog == null && resId != 0) {
+ mProgressDialog = ProgressDialogHelper.get(this, getString(resId)).apply {
+ setCancelable(false)
+ }
+ mProgressDialog?.show()
+ }
+ }
+
+ private fun dismissProgressDialog(){
+ mProgressDialog?.dismiss()
+ mProgressDialog = null
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/DialogHelperActivity.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/DialogHelperActivity.kt
new file mode 100644
index 0000000000..5751066837
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/DialogHelperActivity.kt
@@ -0,0 +1,21 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui
+
+import android.os.Bundle
+import info.nightscout.androidaps.activities.DialogAppCompatActivity
+import info.nightscout.androidaps.core.R
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs.ActivationNotCompleteDialog
+
+class DialogHelperActivity : DialogAppCompatActivity() {
+ @Override
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setTheme(R.style.AppTheme_NoActionBar)
+
+ val dialog = ActivationNotCompleteDialog()
+ dialog.helperActivity = this
+
+ dialog.title = intent.getStringExtra("title")?:""
+ dialog.message = intent.getStringExtra("message")?:""
+ dialog.show(supportFragmentManager, "Dialog")
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EoBaseActivity.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EoBaseActivity.kt
new file mode 100644
index 0000000000..ee6796531a
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EoBaseActivity.kt
@@ -0,0 +1,57 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.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 info.nightscout.androidaps.activities.NoSplashAppCompatActivity
+import info.nightscout.androidaps.core.R
+import info.nightscout.androidaps.plugins.pump.eopatch.dagger.EopatchPluginQualifier
+import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import javax.inject.Inject
+
+abstract class EoBaseActivity : NoSplashAppCompatActivity(), EoBaseNavigator {
+ @Inject
+ @EopatchPluginQualifier
+ 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()
+ }
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EoBaseFragment.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EoBaseFragment.kt
new file mode 100644
index 0000000000..bec613852b
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EoBaseFragment.kt
@@ -0,0 +1,66 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.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.androidaps.plugins.pump.eopatch.dagger.EopatchPluginQualifier
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import javax.inject.Inject
+
+abstract class EoBaseFragment : DaggerFragment(), EoBaseNavigator {
+ @Inject
+ @EopatchPluginQualifier
+ lateinit var viewModelFactory: ViewModelProvider.Factory
+
+ protected var baseActivity: EoBaseActivity<*>? = 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 EoBaseActivity<*>) {
+ 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)
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EoBaseNavigator.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EoBaseNavigator.kt
new file mode 100644
index 0000000000..70b892b5fd
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EoBaseNavigator.kt
@@ -0,0 +1,7 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui
+
+interface EoBaseNavigator {
+ fun back()
+
+ fun finish(finishAffinity: Boolean = false)
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchActivity.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchActivity.kt
new file mode 100644
index 0000000000..d700a87a82
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchActivity.kt
@@ -0,0 +1,392 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui
+
+import android.app.Dialog
+import android.content.Context
+import android.content.Intent
+import android.media.MediaPlayer
+import android.media.RingtoneManager
+import android.os.Bundle
+import android.view.MotionEvent
+import androidx.appcompat.app.AlertDialog
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.androidaps.extensions.safeGetSerializableExtra
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import info.nightscout.androidaps.plugins.pump.eopatch.code.EventType
+import info.nightscout.androidaps.plugins.pump.eopatch.code.PatchLifecycle
+import info.nightscout.androidaps.plugins.pump.eopatch.code.PatchStep
+import info.nightscout.androidaps.plugins.pump.eopatch.databinding.ActivityEopatchBinding
+import info.nightscout.androidaps.plugins.pump.eopatch.extension.replaceFragmentInActivity
+import info.nightscout.androidaps.plugins.pump.eopatch.extension.takeOne
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs.ProgressDialogHelper
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchViewModel
+import info.nightscout.androidaps.utils.alertDialogs.AlertDialogHelper
+
+class EopatchActivity : EoBaseActivity() {
+
+ private var mediaPlayer: MediaPlayer? = null
+ private var mPatchCommCheckDialog: Dialog? = null
+ private var mProgressDialog: AlertDialog? = null
+
+ override fun getLayoutId(): Int = R.layout.activity_eopatch
+
+ override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+ if (event.actionMasked == MotionEvent.ACTION_UP) {
+ binding.viewModel?.updateIncompletePatchActivationReminder()
+ }
+
+ return super.dispatchTouchEvent(event)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ binding.apply {
+ viewModel = ViewModelProvider(this@EopatchActivity, viewModelFactory).get(EopatchViewModel::class.java)
+ viewModel?.apply {
+ processIntent(intent)
+
+ patchStep.observe(this@EopatchActivity) {
+ when (it) {
+ PatchStep.SAFE_DEACTIVATION -> {
+ if (isActivated.value == true) {
+ setupViewFragment(EopatchSafeDeactivationFragment.newInstance())
+ } else {
+ this@EopatchActivity.finish()
+ }
+ }
+
+ PatchStep.MANUALLY_TURNING_OFF_ALARM -> setupViewFragment(EopatchTurningOffAlarmFragment.newInstance())
+ PatchStep.DISCARDED_FOR_CHANGE,
+ PatchStep.DISCARDED_FROM_ALARM,
+ PatchStep.DISCARDED -> setupViewFragment(EopatchRemoveFragment.newInstance())
+ PatchStep.WAKE_UP -> setupViewFragment(EopatchWakeUpFragment.newInstance())
+ PatchStep.CONNECT_NEW -> setupViewFragment(EopatchConnectNewFragment.newInstance())
+ PatchStep.REMOVE_NEEDLE_CAP -> setupViewFragment(EopatchRemoveNeedleCapFragment.newInstance())
+ PatchStep.REMOVE_PROTECTION_TAPE -> setupViewFragment(EopatchRemoveProtectionTapeFragment.newInstance())
+ PatchStep.SAFETY_CHECK -> setupViewFragment(EopatchSafetyCheckFragment.newInstance())
+ PatchStep.ROTATE_KNOB_NEEDLE_INSERTION_ERROR,
+ PatchStep.ROTATE_KNOB -> setupViewFragment(EopatchRotateKnobFragment.newInstance())
+ PatchStep.BASAL_SCHEDULE -> setupViewFragment(EopatchBasalScheduleFragment.newInstance())
+ // PatchStep.SETTING_REMINDER_TIME -> setupViewFragment(PatchExpirationReminderSettingFragment.newInstance())
+ PatchStep.CHECK_CONNECTION -> {
+ checkCommunication(
+ {
+ setResult(RESULT_OK)
+ this@EopatchActivity.finish()
+ }, {
+ setResult(RESULT_CANCELED)
+ this@EopatchActivity.finish()
+ }, {
+ setResult(RESULT_DISCARDED)
+
+ if (intent.getBooleanExtra(EXTRA_GO_HOME, true)) {
+ backToHome(false)
+ } else {
+ this@EopatchActivity.finish()
+ }
+ })
+ }
+
+ PatchStep.COMPLETE -> backToHome(true)
+
+ PatchStep.FINISH -> {
+ if (!intent.getBooleanExtra(EXTRA_START_FROM_MENU, false)
+ || intent.getBooleanExtra(EXTRA_GO_HOME, true)
+ ) {
+ backToHome(false)
+ } else {
+ this@EopatchActivity.finish()
+ }
+ }
+
+ PatchStep.BACK_TO_HOME -> backToHome(false)
+ PatchStep.CANCEL -> this@EopatchActivity.finish()
+ else -> 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)
+
+ forceDiscard = intent.getBooleanExtra(EXTRA_FORCE_DISCARD, false)
+ if (intent.getBooleanExtra(EXTRA_START_WITH_COMM_CHECK, false)) {
+ checkCommunication({
+ initializePatchStep(step)
+ }, {
+ setResult(RESULT_CANCELED)
+ this@EopatchActivity.finish()
+ }, {
+ setResult(RESULT_DISCARDED)
+ this@EopatchActivity.finish()
+ }, doPreCheck = true)
+ } else {
+ initializePatchStep(step)
+ }
+ }
+
+ eventHandler.observe(this@EopatchActivity) { evt ->
+ when (evt.peekContent()) {
+ EventType.SHOW_PATCH_COMM_DIALOG -> {
+ if (mProgressDialog == null) {
+ mProgressDialog = ProgressDialogHelper.get(this@EopatchActivity, getString(evt.value as Int)).apply {
+ setCancelable(false)
+ }
+ mProgressDialog?.show()
+ }
+ }
+
+ EventType.DISMISS_PATCH_COMM_DIALOG -> {
+ dismissProgressDialog()
+ // dismissRetryDialog()
+ }
+
+ EventType.SHOW_PATCH_COMM_ERROR_DIALOG -> {
+ dismissRetryDialog()
+ if (patchStep.value?.isSafeDeactivation == true || connectionTryCnt >= 2) {
+ val cancelLabel = commCheckCancelLabel.value ?: getString(R.string.cancel)
+ val message = "${getString(R.string.patch_comm_error_during_discard_desc_2)}\n${getString(R.string.patch_communication_check_helper_2)}"
+ mPatchCommCheckDialog = AlertDialogHelper.Builder(this@EopatchActivity)
+ .setTitle(R.string.patch_communication_failed)
+ .setMessage(message)
+ .setCancelable(false)
+ .setPositiveButton(R.string.discard) { _, _ ->
+ discardPatch()
+ }
+ .setNegativeButton(cancelLabel) { _, _ ->
+ cancelPatchCommCheck()
+ }
+ .show()
+ } else {
+ val cancelLabel = commCheckCancelLabel.value ?: getString(R.string.cancel)
+ val message = "${getString(R.string.patch_communication_check_helper_1)}\n${getString(R.string.patch_communication_check_helper_2)}"
+ mPatchCommCheckDialog = AlertDialogHelper.Builder(this@EopatchActivity)
+ .setTitle(R.string.patch_communication_failed)
+ .setMessage(message)
+ .setCancelable(false)
+ .setPositiveButton(R.string.retry) { _, _ ->
+ retryCheckCommunication()
+ }
+ .setNegativeButton(cancelLabel) { _, _ ->
+ cancelPatchCommCheck()
+ }
+ .show()
+ }
+ }
+
+ EventType.SHOW_BONDED_DIALOG -> {
+ dismissProgressDialog()
+ AlertDialogHelper.Builder(this@EopatchActivity)
+ .setTitle(R.string.patch_communication_succeed)
+ .setMessage(R.string.patch_communication_succeed_message)
+ .setPositiveButton(R.string.confirm) { _, _ ->
+ dismissPatchCommCheckDialogInternal(true)
+ }.show()
+ }
+
+ EventType.SHOW_CHANGE_PATCH_DIALOG -> {
+ AlertDialogHelper.Builder(this@EopatchActivity).apply {
+ setTitle(R.string.string_discard_patch)
+ setMessage(
+ when {
+ patchState.isBolusActive && patchState.isTempBasalActive -> {
+ R.string.patch_change_confirm_bolus_and_temp_basal_are_active_desc
+ }
+
+ patchState.isBolusActive -> R.string.patch_change_confirm_bolus_is_active_desc
+ patchState.isTempBasalActive -> R.string.patch_change_confirm_temp_basal_is_active_desc
+ else -> R.string.patch_change_confirm_desc
+ }
+ )
+ setPositiveButton(R.string.string_discard_patch) { _, _ ->
+ deactivatePatch()
+ }
+ setNegativeButton(R.string.cancel) { _, _ ->
+
+ }
+ }.show()
+ }
+ // EventType.SHOW_BONDED_DIALOG -> this@EopatchActivity.finish()
+ EventType.SHOW_DISCARD_DIALOG -> {
+ AlertDialogHelper.Builder(this@EopatchActivity).apply {
+ setTitle(R.string.string_discard_patch)
+ if (isBolusActive) {
+ setMessage(R.string.patch_change_confirm_bolus_is_active_desc)
+ } else {
+ setMessage(R.string.string_are_you_sure_to_discard_current_patch)
+ }
+ setPositiveButton(R.string.discard) { _, _ ->
+ deactivate(true) {
+ dismissPatchCommCheckDialogInternal()
+
+ try {
+ moveStep(isConnected.takeOne(PatchStep.DISCARDED, PatchStep.MANUALLY_TURNING_OFF_ALARM))
+ } catch (e: IllegalStateException) {
+ this@EopatchActivity.finish()
+ }
+ }
+ }
+ setNegativeButton(R.string.cancel) { _, _ ->
+ dismissProgressDialog()
+ updateIncompletePatchActivationReminder()
+ }
+ }.show()
+
+ }
+
+ else -> Unit
+ }
+ }
+ }
+ }
+
+ private fun dismissProgressDialog(){
+ mProgressDialog?.let {
+ try {
+ mProgressDialog?.dismiss()
+ } catch (e: IllegalStateException) {
+ }
+ mProgressDialog = null
+ }
+ }
+
+ private fun dismissRetryDialog(){
+ mPatchCommCheckDialog?.let {
+ try {
+ mPatchCommCheckDialog?.dismiss()
+ } catch (e: IllegalStateException) {
+ }
+ mPatchCommCheckDialog = null
+ }
+ }
+
+ private fun backToHome(isActivated: Boolean) {
+ if (isActivated) {
+ mediaPlayer = MediaPlayer.create(this, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))?.apply {
+ setOnCompletionListener {
+ this@EopatchActivity.finish()
+ }
+ start()
+ return
+ }
+ }
+
+ this@EopatchActivity.finish()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+
+ mediaPlayer?.let {
+ it.stop()
+ it.release()
+ mediaPlayer = null
+ }
+ }
+
+ override fun onBackPressed() {
+ binding.viewModel?.apply{
+ when(patchStep.value){
+ PatchStep.SAFE_DEACTIVATION -> this@EopatchActivity.finish()
+ else -> Unit
+ }
+ }
+ }
+
+ companion object {
+ const val RESULT_DISCARDED = RESULT_FIRST_USER + 1
+ const val EXTRA_START_PATCH_STEP = "EXTRA_START_PATCH_FRAGMENT_UI"
+ const val EXTRA_START_FROM_MENU = "EXTRA_START_FROM_MENU"
+ const val EXTRA_START_WITH_COMM_CHECK = "EXTRA_START_WITH_COMM_CHECK"
+ const val EXTRA_GO_HOME = "EXTRA_GO_HOME"
+ const val EXTRA_FORCE_DISCARD = "EXTRA_FORCE_DISCARD"
+ const val NORMAL_TEMPERATURE_MIN = 4
+ const val NORMAL_TEMPERATURE_MAX = 45
+
+ @JvmStatic
+ @JvmOverloads
+ fun createIntentForCheckConnection(context: Context, goHomeAfterDiscard: Boolean = true, forceDiscard: Boolean = false): Intent {
+ return Intent(context, EopatchActivity::class.java).apply {
+ putExtra(EXTRA_START_PATCH_STEP, PatchStep.CHECK_CONNECTION)
+ putExtra(EXTRA_GO_HOME, goHomeAfterDiscard)
+ putExtra(EXTRA_FORCE_DISCARD, forceDiscard)
+ }
+ }
+
+ @JvmStatic
+ fun createIntentForChangePatch(context: Context): Intent {
+ return createIntent(context, PatchStep.SAFE_DEACTIVATION, false)
+ }
+
+ @JvmStatic
+ @JvmOverloads
+ fun createIntentForDiscarded(context: Context, goHome: Boolean = true): Intent {
+ return createIntent(context, PatchStep.DISCARDED_FROM_ALARM, false).apply {
+ putExtra(EXTRA_GO_HOME, goHome)
+ }
+ }
+
+ @JvmStatic
+ fun createIntentForCannulaInsertionError(context: Context): Intent {
+ return createIntent(context, PatchStep.ROTATE_KNOB_NEEDLE_INSERTION_ERROR, false)
+ }
+
+ @JvmStatic
+ @JvmOverloads
+ fun createIntent(context: Context, patchStep: PatchStep, doCommCheck: Boolean = true): Intent {
+ return Intent(context, EopatchActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
+ putExtra(EXTRA_START_PATCH_STEP, patchStep)
+ putExtra(EXTRA_START_WITH_COMM_CHECK, doCommCheck)
+ }
+ }
+
+ @JvmStatic
+ fun createIntentFromMenu(context: Context, patchStep: PatchStep): Intent {
+ return Intent(context, EopatchActivity::class.java).apply {
+ putExtra(EXTRA_START_PATCH_STEP, patchStep)
+ putExtra(EXTRA_START_FROM_MENU, true)
+ }
+ }
+
+ @JvmStatic
+ fun createIntent(context: Context, lifecycle: PatchLifecycle, doCommCheck: Boolean): Intent? {
+ return when (lifecycle) {
+ PatchLifecycle.SHUTDOWN -> {
+ // if (PatchConfig().hasMacAddress()) {
+ // createIntent(context, PatchStep.SAFE_DEACTIVATION, doCommCheck)
+ // } else {
+ createIntent(context, PatchStep.WAKE_UP, false)
+ // }
+ }
+ PatchLifecycle.BONDED -> createIntent(context, PatchStep.CONNECT_NEW, doCommCheck)
+ PatchLifecycle.REMOVE_NEEDLE_CAP -> createIntent(context, PatchStep.REMOVE_NEEDLE_CAP, doCommCheck)
+ PatchLifecycle.REMOVE_PROTECTION_TAPE -> createIntent(context, PatchStep.REMOVE_PROTECTION_TAPE, doCommCheck)
+ PatchLifecycle.SAFETY_CHECK -> createIntent(context, PatchStep.SAFETY_CHECK, doCommCheck)
+ PatchLifecycle.ROTATE_KNOB -> {
+ // val nextStep = PatchConfig().rotateKnobNeedleSensingError.takeOne(
+ // PatchStep.ROTATE_KNOB_NEEDLE_INSERTION_ERROR, PatchStep.ROTATE_KNOB)
+ // createIntent(context, nextStep, doCommCheck)
+ createIntent(context, PatchStep.ROTATE_KNOB, doCommCheck)
+ }
+ PatchLifecycle.BASAL_SETTING -> createIntent(context, PatchStep.ROTATE_KNOB, doCommCheck)
+ else -> null
+ }
+ }
+ }
+
+ private fun setupViewFragment(baseFragment: EoBaseFragment<*>) {
+ replaceFragmentInActivity(baseFragment, R.id.framelayout_fragment, false)
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchBasalScheduleFragment.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchBasalScheduleFragment.kt
new file mode 100644
index 0000000000..dbc40c87c4
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchBasalScheduleFragment.kt
@@ -0,0 +1,25 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import info.nightscout.androidaps.plugins.pump.eopatch.databinding.FragmentEopatchBasalScheduleBinding
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchViewModel
+
+class EopatchBasalScheduleFragment : EoBaseFragment() {
+
+ companion object {
+ fun newInstance(): EopatchBasalScheduleFragment = EopatchBasalScheduleFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_eopatch_basal_schedule
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(EopatchViewModel::class.java)
+ viewModel?.initPatchStep()
+ }
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchConnectNewFragment.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchConnectNewFragment.kt
new file mode 100644
index 0000000000..669ddb6734
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchConnectNewFragment.kt
@@ -0,0 +1,41 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchViewModel.SetupStep.*
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import info.nightscout.androidaps.plugins.pump.eopatch.code.PatchStep
+import info.nightscout.androidaps.plugins.pump.eopatch.databinding.FragmentEopatchConnectNewBinding
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchViewModel
+import info.nightscout.androidaps.utils.ToastUtils
+
+class EopatchConnectNewFragment : EoBaseFragment() {
+
+ companion object {
+ fun newInstance(): EopatchConnectNewFragment = EopatchConnectNewFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_eopatch_connect_new
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(EopatchViewModel::class.java)
+ viewModel?.apply {
+ setupStep.observe(viewLifecycleOwner) {
+ when (it) {
+ SCAN_FAILED,
+ BONDING_FAILED -> checkCommunication({ retryScan() }, { moveStep(PatchStep.WAKE_UP) })
+ GET_PATCH_INFO_FAILED -> checkCommunication({ getPatchInfo() }, { moveStep(PatchStep.WAKE_UP) })
+ SELF_TEST_FAILED -> checkCommunication({ selfTest() }, { moveStep(PatchStep.WAKE_UP) })
+ ACTIVATION_FAILED -> ToastUtils.errorToast(requireContext(), "Activation failed!")
+ else -> Unit
+ }
+ }
+
+ startScan()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchOverviewFragment.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchOverviewFragment.kt
new file mode 100644
index 0000000000..3b4cd0986c
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchOverviewFragment.kt
@@ -0,0 +1,184 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.lifecycle.ViewModelProvider
+import dagger.android.support.DaggerAppCompatActivity
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import info.nightscout.androidaps.plugins.pump.eopatch.core.code.BolusType
+import info.nightscout.androidaps.plugins.pump.eopatch.code.PatchStep
+import info.nightscout.androidaps.plugins.pump.eopatch.code.EventType
+import info.nightscout.androidaps.plugins.pump.eopatch.databinding.FragmentEopatchOverviewBinding
+import info.nightscout.androidaps.plugins.pump.eopatch.extension.takeOne
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchOverviewViewModel
+import info.nightscout.androidaps.utils.ToastUtils
+import info.nightscout.androidaps.utils.alertDialogs.AlertDialogHelper
+import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.logging.AAPSLogger
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import javax.inject.Inject
+
+class EopatchOverviewFragment: EoBaseFragment() {
+ @Inject lateinit var rxBus: RxBus
+ @Inject lateinit var aapsSchedulers: AapsSchedulers
+ @Inject lateinit var aapsLogger: AAPSLogger
+ private lateinit var resultLauncherForResume: ActivityResultLauncher
+ private lateinit var resultLauncherForPause: ActivityResultLauncher
+
+ private var disposable: CompositeDisposable = CompositeDisposable()
+ private var pauseDuration = 0.5f
+
+ override fun getLayoutId(): Int = R.layout.fragment_eopatch_overview
+
+ override fun onDestroy() {
+ super.onDestroy()
+ disposable.clear()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ binding.apply {
+ viewmodel = ViewModelProvider(this@EopatchOverviewFragment, viewModelFactory).get(EopatchOverviewViewModel::class.java)
+ viewmodel?.apply {
+ eventHandler.observe(viewLifecycleOwner) { evt ->
+ when (evt.peekContent()) {
+ EventType.ACTIVATION_CLICKED -> requireContext().apply { startActivity(EopatchActivity.createIntentFromMenu(this, PatchStep.WAKE_UP)) }
+ EventType.DEACTIVATION_CLICKED -> requireContext().apply { startActivity(EopatchActivity.createIntentForChangePatch(this)) }
+ EventType.SUSPEND_CLICKED -> suspend()
+ EventType.RESUME_CLICKED -> resume()
+ EventType.INVALID_BASAL_RATE -> ToastUtils.infoToast(requireContext(), R.string.invalid_basal_rate)
+ EventType.PROFILE_NOT_SET -> ToastUtils.infoToast(requireContext(), R.string.no_profile_selected)
+ EventType.PAUSE_BASAL_SUCCESS -> ToastUtils.infoToast(requireContext(), R.string.string_suspended_insulin_delivery_message)
+ EventType.PAUSE_BASAL_FAILED -> ToastUtils.errorToast(requireContext(), R.string.string_pause_failed)
+ EventType.RESUME_BASAL_SUCCESS -> ToastUtils.infoToast(requireContext(), R.string.string_resumed_insulin_delivery_message)
+ EventType.RESUME_BASAL_FAILED -> ToastUtils.errorToast(requireContext(), R.string.string_resume_failed)
+ else -> Unit
+ }
+ }
+
+ resultLauncherForResume = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
+ when (it.resultCode) {
+ DaggerAppCompatActivity.RESULT_OK -> resumeBasal()
+ DaggerAppCompatActivity.RESULT_CANCELED -> ToastUtils.errorToast(requireContext(), R.string.string_resume_failed)
+ }
+ }
+
+ resultLauncherForPause = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
+ when (it.resultCode) {
+ DaggerAppCompatActivity.RESULT_OK -> {
+ pauseBasal(pauseDuration)
+ pauseDuration = 0.5f
+ }
+ DaggerAppCompatActivity.RESULT_CANCELED -> ToastUtils.errorToast(requireContext(), R.string.string_pause_failed)
+ }
+ }
+ }
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ binding.viewmodel?.stopBasalRateUpdate()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ binding.viewmodel?.startBasalRateUpdate()
+ }
+
+ private fun suspend() {
+ binding.viewmodel?.apply {
+ activity?.let {
+ val builder = AlertDialogHelper.Builder(it)
+ val msg = getSuspendDialogText()
+
+ val dialog = builder.setTitle(R.string.string_suspend)
+ .setMessage(msg)
+ .setPositiveButton(R.string.confirm) { _, _ ->
+ openPauseTimePicker()
+ }
+ .setNegativeButton(R.string.cancel) { _, _ ->
+
+ }.create()
+ dialog.show()
+ }
+ }
+ }
+
+ private fun resume() {
+ binding.viewmodel?.apply {
+ activity?.let {
+ val builder = AlertDialogHelper.Builder(it)
+ val dialog = builder.setTitle(R.string.string_resume_insulin_delivery_title)
+ .setMessage(R.string.string_resume_insulin_delivery_message)
+ .setPositiveButton(R.string.confirm) { _, _ ->
+ if (isPatchConnected) {
+ resumeBasal()
+ } else {
+ resultLauncherForResume.launch(EopatchActivity.createIntentForCheckConnection(requireContext(), true))
+ }
+ }
+ .setNegativeButton(R.string.cancel) { _, _ ->
+
+ }.create()
+ dialog.show()
+ }
+ }
+ }
+
+ private fun openPauseTimePicker() {
+ binding.viewmodel?.apply {
+ activity?.let{
+ val builder = AlertDialogHelper.Builder(it)
+ val listArr = requireContext().resources.getStringArray(R.array.suspend_duration_array)
+ var select = 0
+ val dialog = builder.setTitle(R.string.string_suspend_time_insulin_delivery_title)
+ .setSingleChoiceItems(listArr, 0) { _, which ->
+ select = which
+ }
+ .setPositiveButton(R.string.confirm) { _, _ ->
+ if (isPatchConnected) {
+ pauseBasal((select + 1) * 0.5f)
+ } else {
+ pauseDuration = (select + 1) * 0.5f
+ resultLauncherForPause.launch(EopatchActivity.createIntentForCheckConnection(requireContext(), true))
+ }
+ }
+ .setNegativeButton(R.string.cancel) { _, _ ->
+
+ }.create()
+ dialog.show()
+ }
+ }
+ }
+
+ private fun getSuspendDialogText(): String{
+ binding.viewmodel?.apply {
+ val isBolusActive = patchManager.patchState.isBolusActive
+ val isTempBasalActive = patchManager.patchState.isTempBasalActive
+ val tempRate = patchManager.preferenceManager.getTempBasalManager().startedBasal?.doseUnitText ?: ""
+ val tempRemainTime = patchManager.preferenceManager.getTempBasalManager().startedBasal?.remainTimeText ?: ""
+ var remainBolus = patchManager.patchState.isNowBolusActive.takeOne(patchManager.bolusCurrent.remain(BolusType.NOW), 0f)
+ remainBolus += patchManager.patchState.isExtBolusActive.takeOne(patchManager.bolusCurrent.remain(BolusType.EXT), 0f)
+
+ val sbMsg = StringBuilder()
+
+ if (isBolusActive && isTempBasalActive) {
+ sbMsg.append(getString(R.string.insulin_suspend_msg1, tempRate, tempRemainTime, remainBolus))
+ } else if (isBolusActive) {
+ sbMsg.append(getString(R.string.insulin_suspend_msg2, remainBolus))
+ } else if (isTempBasalActive) {
+ sbMsg.append(getString(R.string.insulin_suspend_msg3, tempRate, tempRemainTime))
+ } else {
+ sbMsg.append(getString(R.string.insulin_suspend_msg4))
+ }
+ return sbMsg.toString()
+ }
+ return ""
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchRemoveFragment.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchRemoveFragment.kt
new file mode 100644
index 0000000000..45ea3dbf13
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchRemoveFragment.kt
@@ -0,0 +1,22 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import info.nightscout.androidaps.plugins.pump.eopatch.databinding.FragmentEopatchRemoveBinding
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchViewModel
+
+class EopatchRemoveFragment : EoBaseFragment() {
+
+ companion object {
+ fun newInstance(): EopatchRemoveFragment = EopatchRemoveFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_eopatch_remove
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(EopatchViewModel::class.java)
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchRemoveNeedleCapFragment.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchRemoveNeedleCapFragment.kt
new file mode 100644
index 0000000000..b8cfe53ba2
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchRemoveNeedleCapFragment.kt
@@ -0,0 +1,22 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import info.nightscout.androidaps.plugins.pump.eopatch.databinding.FragmentEopatchRemoveNeedleCapBinding
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchViewModel
+
+class EopatchRemoveNeedleCapFragment : EoBaseFragment() {
+
+ companion object {
+ fun newInstance(): EopatchRemoveNeedleCapFragment = EopatchRemoveNeedleCapFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_eopatch_remove_needle_cap
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(EopatchViewModel::class.java)
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchRemoveProtectionTapeFragment.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchRemoveProtectionTapeFragment.kt
new file mode 100644
index 0000000000..85d822dc61
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchRemoveProtectionTapeFragment.kt
@@ -0,0 +1,22 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import info.nightscout.androidaps.plugins.pump.eopatch.databinding.FragmentEopatchRemoveProtectionTapeBinding
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchViewModel
+
+class EopatchRemoveProtectionTapeFragment : EoBaseFragment() {
+
+ companion object {
+ fun newInstance(): EopatchRemoveProtectionTapeFragment = EopatchRemoveProtectionTapeFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_eopatch_remove_protection_tape
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(EopatchViewModel::class.java)
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchRotateKnobFragment.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchRotateKnobFragment.kt
new file mode 100644
index 0000000000..9f1920a548
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchRotateKnobFragment.kt
@@ -0,0 +1,69 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui
+
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.updateLayoutParams
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import info.nightscout.androidaps.plugins.pump.eopatch.code.PatchStep
+import info.nightscout.androidaps.plugins.pump.eopatch.databinding.FragmentEopatchRotateKnobBinding
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchViewModel
+import info.nightscout.androidaps.utils.ToastUtils
+
+class EopatchRotateKnobFragment : EoBaseFragment() {
+
+ companion object {
+ fun newInstance(): EopatchRotateKnobFragment = EopatchRotateKnobFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_eopatch_rotate_knob
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(EopatchViewModel::class.java)
+ viewModel?.apply {
+ initPatchStep()
+
+ if (patchStep.value == PatchStep.ROTATE_KNOB_NEEDLE_INSERTION_ERROR) {
+ btnNegative.visibility = View.VISIBLE
+ guidelineButton.setGuidelinePercent(0.4f)
+
+ btnPositive.apply {
+ updateLayoutParams { leftMargin = 3 }
+ text = getString(R.string.retry)
+ }
+ textPatchRotateKnobDesc.visibility = View.VISIBLE
+ layoutNeedleInsertionError.visibility = View.VISIBLE
+ textRotateKnobDesc2.visibility = View.GONE
+ textRotateKnobDesc2NeedleInsertionError.visibility = View.VISIBLE
+ textActivationErrorDesc.visibility = View.GONE
+ }
+
+ setupStep.observe(viewLifecycleOwner) {
+ when (it) {
+ EopatchViewModel.SetupStep.NEEDLE_SENSING_FAILED -> {
+ checkCommunication({ startNeedleSensing() })
+ }
+ EopatchViewModel.SetupStep.ACTIVATION_FAILED -> {
+ btnNegative.visibility = View.VISIBLE
+ guidelineButton.setGuidelinePercent(0.4f)
+
+ btnPositive.apply {
+ updateLayoutParams { leftMargin = 3 }
+ text = getString(R.string.retry)
+ }
+ textPatchRotateKnobDesc.visibility = View.GONE
+ textRotateKnobDesc2.visibility = View.GONE
+ textRotateKnobDesc2NeedleInsertionError.visibility = View.VISIBLE
+ textActivationErrorDesc.visibility = View.VISIBLE
+ ToastUtils.errorToast(requireContext(), "Activation failed!")
+ }
+ else -> Unit
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchSafeDeactivationFragment.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchSafeDeactivationFragment.kt
new file mode 100644
index 0000000000..16d3ec0992
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchSafeDeactivationFragment.kt
@@ -0,0 +1,24 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import info.nightscout.androidaps.plugins.pump.eopatch.databinding.FragmentEopatchSafeDeativationBinding
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchViewModel
+
+class EopatchSafeDeactivationFragment : EoBaseFragment() {
+
+ companion object {
+ fun newInstance(): EopatchSafeDeactivationFragment = EopatchSafeDeactivationFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_eopatch_safe_deativation
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(EopatchViewModel::class.java).apply {
+ updateExpirationTime()
+ }
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchSafetyCheckFragment.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchSafetyCheckFragment.kt
new file mode 100644
index 0000000000..6ac1d9246e
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchSafetyCheckFragment.kt
@@ -0,0 +1,38 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import info.nightscout.androidaps.plugins.pump.eopatch.code.PatchStep
+import info.nightscout.androidaps.plugins.pump.eopatch.databinding.FragmentEopatchSafetyCheckBinding
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchViewModel
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchViewModel.SetupStep.*
+
+class EopatchSafetyCheckFragment : EoBaseFragment() {
+
+ companion object {
+ fun newInstance(): EopatchSafetyCheckFragment = EopatchSafetyCheckFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_eopatch_safety_check
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(EopatchViewModel::class.java)
+ viewModel?.apply {
+ initPatchStep()
+
+ setupStep.observe(viewLifecycleOwner) {
+ when (it) {
+ SAFETY_CHECK_FAILED -> checkCommunication({ retrySafetyCheck() }, { moveStep(PatchStep.SAFETY_CHECK) })
+ else -> Unit
+ }
+ }
+
+ startSafetyCheck()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchTurningOffAlarmFragment.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchTurningOffAlarmFragment.kt
new file mode 100644
index 0000000000..edaada43ca
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchTurningOffAlarmFragment.kt
@@ -0,0 +1,22 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import info.nightscout.androidaps.plugins.pump.eopatch.databinding.FragmentEopatchTurningOffAlarmBinding
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchViewModel
+
+class EopatchTurningOffAlarmFragment : EoBaseFragment() {
+
+ companion object {
+ fun newInstance(): EopatchTurningOffAlarmFragment = EopatchTurningOffAlarmFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_eopatch_turning_off_alarm
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(EopatchViewModel::class.java)
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchWakeUpFragment.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchWakeUpFragment.kt
new file mode 100644
index 0000000000..c95fcedd8b
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/EopatchWakeUpFragment.kt
@@ -0,0 +1,25 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.lifecycle.ViewModelProvider
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import info.nightscout.androidaps.plugins.pump.eopatch.databinding.FragmentEopatchWakeUpBinding
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchViewModel
+
+class EopatchWakeUpFragment : EoBaseFragment() {
+
+ companion object {
+ fun newInstance(): EopatchWakeUpFragment = EopatchWakeUpFragment()
+ }
+
+ override fun getLayoutId(): Int = R.layout.fragment_eopatch_wake_up
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.apply {
+ viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(EopatchViewModel::class.java)
+ viewModel?.initPatchStep()
+ }
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/dialogs/ActivationNotCompleteDialog.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/dialogs/ActivationNotCompleteDialog.kt
new file mode 100644
index 0000000000..78b70ccd4c
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/dialogs/ActivationNotCompleteDialog.kt
@@ -0,0 +1,80 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs
+
+import android.os.Bundle
+import android.view.*
+import dagger.android.support.DaggerDialogFragment
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.pump.eopatch.bindingadapters.setOnSafeClickListener
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPatchManager
+import info.nightscout.androidaps.plugins.pump.eopatch.databinding.DialogCommonBinding
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.DialogHelperActivity
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.EopatchActivity
+import javax.inject.Inject
+
+class ActivationNotCompleteDialog : DaggerDialogFragment() {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var patchManager: IPatchManager
+ @Inject lateinit var rxBus: RxBus
+
+ var helperActivity: DialogHelperActivity? = null
+ var message: String = ""
+ var title: String = ""
+
+ private var _binding: DialogCommonBinding? = null
+ private val binding get() = _binding!!
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?): View {
+ dialog?.window?.requestFeature(Window.FEATURE_NO_TITLE)
+ dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
+ isCancelable = false
+ dialog?.setCanceledOnTouchOutside(false)
+
+ savedInstanceState?.let { bundle ->
+ bundle.getString("title")?.let { title = it }
+ bundle.getString("message")?.let { message = it }
+ }
+ _binding = DialogCommonBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ binding.title.text = title
+ binding.ok.setOnSafeClickListener {
+ helperActivity?.apply {
+ startActivity(EopatchActivity.createIntent(this, patchManager.patchConfig.lifecycleEvent.lifeCycle, false))
+ }
+ dismiss()
+ }
+ }
+
+ override fun onSaveInstanceState(bundle: Bundle) {
+ super.onSaveInstanceState(bundle)
+ bundle.putString("message", message)
+ bundle.putString("title", title)
+ }
+
+ override fun onStart() {
+ super.onStart()
+ dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ binding.message.text = message
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ override fun dismiss() {
+ super.dismissAllowingStateLoss()
+ helperActivity?.finish()
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/dialogs/AlarmDialog.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/dialogs/AlarmDialog.kt
new file mode 100644
index 0000000000..19e08c53c3
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/dialogs/AlarmDialog.kt
@@ -0,0 +1,174 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs
+
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import android.os.HandlerThread
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.Window
+import android.view.WindowManager
+import dagger.android.support.DaggerDialogFragment
+import info.nightscout.androidaps.core.R
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmProcess
+import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmProcess
+import info.nightscout.androidaps.plugins.pump.eopatch.bindingadapters.setOnSafeClickListener
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPatchManager
+import info.nightscout.androidaps.plugins.pump.eopatch.databinding.DialogAlarmBinding
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.AlarmHelperActivity
+import info.nightscout.androidaps.services.AlarmSoundServiceHelper
+import info.nightscout.androidaps.utils.T
+import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.logging.AAPSLogger
+import io.reactivex.rxjava3.disposables.Disposable
+import javax.inject.Inject
+
+class AlarmDialog : DaggerDialogFragment() {
+
+ @Inject lateinit var alarmSoundServiceHelper: AlarmSoundServiceHelper
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var patchManager: IPatchManager
+ @Inject lateinit var rxBus: RxBus
+ @Inject lateinit var aapsSchedulers: AapsSchedulers
+
+ var helperActivity: AlarmHelperActivity? = null
+ var alarmCode: AlarmCode? = null
+ var code: String? = null
+ var status: String = ""
+ var title: String = ""
+ var sound: Int = 0
+
+ private lateinit var mAlarmProcess: IAlarmProcess
+ private var handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper)
+
+ private var _binding: DialogAlarmBinding? = null
+ private var disposable: Disposable? = null
+ private val binding get() = _binding!!
+
+ private var isHolding = false
+ private var isMute = false
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?): View {
+ mAlarmProcess = AlarmProcess(patchManager, rxBus)
+
+ dialog?.window?.requestFeature(Window.FEATURE_NO_TITLE)
+ dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
+ isCancelable = false
+ dialog?.setCanceledOnTouchOutside(false)
+
+ savedInstanceState?.let { bundle ->
+ bundle.getString("status")?.let { status = it }
+ bundle.getString("title")?.let { title = it }
+ bundle.getString("code")?.let {
+ code = it
+ alarmCode = AlarmCode.fromStringToCode(it)
+ }
+ sound = bundle.getInt("sound", R.raw.error)
+ }
+ aapsLogger.debug("Alarm dialog displayed")
+ _binding = DialogAlarmBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ binding.title.text = title
+ binding.ok.setOnSafeClickListener {
+ aapsLogger.debug("USER ENTRY: Alarm dialog ok button pressed")
+ alarmCode?.let { ac ->
+ mAlarmProcess.doAction(requireContext(), ac)
+ .subscribeOn(aapsSchedulers.io)
+ .subscribe ({ ret ->
+ aapsLogger.debug("Alarm processing result :${ret}")
+ if (ret == IAlarmProcess.ALARM_HANDLED) {
+ alarmCode?.let{
+ patchManager.preferenceManager.getAlarms().handle(it)
+ patchManager.preferenceManager.flushAlarms()
+ }
+ dismiss()
+ }else if (ret == IAlarmProcess.ALARM_PAUSE) {
+ isHolding = true
+ }else if (ret == IAlarmProcess.ALARM_UNHANDLED) {
+ if(!isMute){
+ startAlarm("ALARM_UNHANDLED")
+ }
+ }
+ }, { t -> aapsLogger.error("${t.printStackTrace()}") })
+ }
+ stopAlarm("OK clicked")
+ }
+ binding.mute.setOnSafeClickListener {
+ aapsLogger.debug("USER ENTRY: Error dialog mute button pressed")
+ isMute = true
+ stopAlarm("Mute clicked")
+ }
+ binding.mute5min.setOnSafeClickListener {
+ aapsLogger.debug("USER ENTRY: Error dialog mute 5 min button pressed")
+ stopAlarm("Mute5m clicked")
+ isMute = true
+ handler.postDelayed({ startAlarm("post") }, T.mins(5).msecs())
+ }
+ startAlarm("onViewCreated")
+
+ disposable = patchManager.observePatchLifeCycle()
+ .observeOn(aapsSchedulers.main)
+ .subscribe {
+ if(it.isShutdown) {
+ activity?.finish()
+ }
+ }
+
+ }
+
+ override fun onSaveInstanceState(bundle: Bundle) {
+ super.onSaveInstanceState(bundle)
+ bundle.putString("status", status)
+ bundle.putString("title", title)
+ bundle.putInt("sound", sound)
+ }
+
+ override fun onStart() {
+ super.onStart()
+ dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if(isHolding && !isMute){
+ startAlarm("onResume")
+ }
+ binding.status.text = status
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ disposable?.dispose()
+ disposable = null
+ }
+
+ override fun dismiss() {
+ super.dismissAllowingStateLoss()
+ handler.removeCallbacksAndMessages(null)
+ helperActivity?.finish()
+ }
+
+ private fun startAlarm(reason: String) {
+ if (sound != 0) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context?.let { context -> alarmSoundServiceHelper.startAlarm(context, sound, reason) }
+ }
+ }
+ }
+
+ private fun stopAlarm(reason: String) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context?.let { context -> alarmSoundServiceHelper.stopService(context, reason) }
+ }
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/dialogs/CommonDialog.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/dialogs/CommonDialog.kt
new file mode 100644
index 0000000000..2c2775e556
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/dialogs/CommonDialog.kt
@@ -0,0 +1,45 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs
+
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import dagger.android.support.DaggerDialogFragment
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import info.nightscout.androidaps.utils.alertDialogs.AlertDialogHelper
+import java.lang.IllegalStateException
+import javax.inject.Inject
+
+class CommonDialog : DaggerDialogFragment() {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+
+ var title: Int = 0
+ var message: Int = 0
+ var positiveBtn: Int = R.string.confirm
+ var negativeBtn: Int = 0
+
+ var positiveListener: DialogInterface.OnClickListener? = null
+ var negativeListener: DialogInterface.OnClickListener? = null
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return activity?.let{
+ val builder = AlertDialogHelper.Builder(it).apply {
+ if(title != 0) setTitle(title)
+ if(message != 0) setMessage(message)
+ setPositiveButton(positiveBtn,
+ positiveListener?:DialogInterface.OnClickListener { _, _ ->
+ dismiss()
+ })
+ if(negativeBtn != 0) {
+ setNegativeButton(negativeBtn,
+ negativeListener ?: DialogInterface.OnClickListener { _, _ ->
+ dismiss()
+ })
+ }
+ }
+
+ builder.create()
+ } ?: throw IllegalStateException("Activity is null")
+ }
+}
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/dialogs/ProgressDialogHelper.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/dialogs/ProgressDialogHelper.kt
new file mode 100644
index 0000000000..c79d450a0b
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/dialogs/ProgressDialogHelper.kt
@@ -0,0 +1,61 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs
+
+import android.content.Context
+import android.graphics.Color
+import android.view.Gravity
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.widget.LinearLayout
+import android.widget.ProgressBar
+import android.widget.TextView
+import androidx.appcompat.app.AlertDialog
+
+class ProgressDialogHelper {
+ companion object {
+ fun get(context:Context, message:String): AlertDialog {
+ val llPadding = 30
+ val linearLayout = LinearLayout(context)
+ linearLayout.orientation = LinearLayout.HORIZONTAL
+ linearLayout.setPadding(llPadding, llPadding, llPadding, llPadding)
+ linearLayout.gravity = Gravity.CENTER
+ var llParam = LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT)
+ llParam.gravity = Gravity.CENTER
+ linearLayout.layoutParams = llParam
+
+ val progressBar = ProgressBar(context)
+ progressBar.isIndeterminate = true
+ progressBar.setPadding(0, 0, llPadding, 0)
+ progressBar.layoutParams = llParam
+
+ llParam = LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT)
+ llParam.gravity = Gravity.CENTER
+ val tvText = TextView(context)
+ tvText.text = message
+ // tvText.setTextColor(Color.parseColor("#000000"))
+ tvText.textSize = 20.toFloat()
+ tvText.layoutParams = llParam
+
+ linearLayout.addView(progressBar)
+ linearLayout.addView(tvText)
+
+ val builder = AlertDialog.Builder(context)
+ builder.setCancelable(true)
+ builder.setView(linearLayout)
+
+ val dialog = builder.create()
+ val window = dialog.window
+ if (window != null) {
+ val layoutParams = WindowManager.LayoutParams()
+ layoutParams.copyFrom(dialog.window?.attributes)
+ layoutParams.width = LinearLayout.LayoutParams.WRAP_CONTENT
+ layoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT
+ dialog.window?.attributes = layoutParams
+ }
+ return dialog
+ }
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/event/SingleLiveEvent.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/event/SingleLiveEvent.kt
new file mode 100644
index 0000000000..499cc34305
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/event/SingleLiveEvent.kt
@@ -0,0 +1,29 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.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
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/event/UIEvent.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/event/UIEvent.kt
new file mode 100644
index 0000000000..5b36648367
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/event/UIEvent.kt
@@ -0,0 +1,8 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui.event
+
+
+open class UIEvent(private val content: T) {
+ var value: Any? = null
+ fun peekContent(): T = content
+}
+
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/receiver/RxBroadcastReceiver.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/receiver/RxBroadcastReceiver.kt
new file mode 100644
index 0000000000..2844866abb
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/receiver/RxBroadcastReceiver.kt
@@ -0,0 +1,62 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui.receiver
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import androidx.annotation.CheckResult
+import io.reactivex.rxjava3.android.MainThreadDisposable
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Observer
+
+class RxBroadcastReceiver private constructor() {
+ internal class BroadcastReceiverObservable : Observable {
+
+ private val context: Context
+ private val intentFilter: IntentFilter
+ private val abortBroadcast: Boolean
+
+ constructor(context: Context, intentFilter: IntentFilter) {
+ this.context = context
+ this.intentFilter = intentFilter
+ abortBroadcast = false
+ }
+
+ constructor(context: Context, intentFilter: IntentFilter, abortBroadcast: Boolean) {
+ this.context = context
+ this.intentFilter = intentFilter
+ this.abortBroadcast = abortBroadcast
+ }
+
+ override fun subscribeActual(observer: Observer) {
+ val listener = Listener(context, observer)
+ observer.onSubscribe(listener)
+ context.registerReceiver(listener.receiver, intentFilter)
+ }
+
+ internal inner class Listener(private val context: Context, private val observer: Observer) : MainThreadDisposable() {
+
+ val receiver: BroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (!isDisposed) {
+ observer.onNext(intent)
+ if (abortBroadcast) abortBroadcast()
+ }
+ }
+ }
+
+ override fun onDispose() {
+ context.unregisterReceiver(receiver)
+ }
+ }
+ }
+
+ companion object {
+ @CheckResult
+ fun create(context: Context, intentFilter: IntentFilter): Observable
+ = BroadcastReceiverObservable(context, intentFilter)
+ @CheckResult
+ fun create(context: Context, intentFilter: IntentFilter, abortBroadcast: Boolean): Observable
+ = BroadcastReceiverObservable(context, intentFilter, abortBroadcast)
+ }
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/viewmodel/EoBaseViewModel.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/viewmodel/EoBaseViewModel.kt
new file mode 100644
index 0000000000..9e30541ef3
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/viewmodel/EoBaseViewModel.kt
@@ -0,0 +1,31 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel
+
+import androidx.lifecycle.ViewModel
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.EoBaseNavigator
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.Disposable
+import java.lang.ref.WeakReference
+
+abstract class EoBaseViewModel : 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) }
+
+}
\ No newline at end of file
diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/viewmodel/EopatchOverviewViewModel.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/viewmodel/EopatchOverviewViewModel.kt
new file mode 100644
index 0000000000..91432fe7da
--- /dev/null
+++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/viewmodel/EopatchOverviewViewModel.kt
@@ -0,0 +1,293 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Transformations
+import info.nightscout.androidaps.interfaces.ProfileFunction
+import info.nightscout.androidaps.interfaces.PumpSync
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPatchManager
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager
+import info.nightscout.androidaps.plugins.pump.eopatch.code.EventType
+import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.BleConnectionState
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.EoBaseNavigator
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.event.SingleLiveEvent
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.event.UIEvent
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.Alarms
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchConfig
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.utils.T
+import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.disposables.Disposable
+import java.util.*
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import kotlin.math.max
+import kotlin.math.roundToInt
+
+class EopatchOverviewViewModel @Inject constructor(
+ private val rh: ResourceHelper,
+ val patchManager: IPatchManager,
+ private val preferenceManager: IPreferenceManager,
+ private val profileFunction: ProfileFunction,
+ private val aapsSchedulers: AapsSchedulers,
+ private val aapsLogger: AAPSLogger,
+ private val dateUtil: DateUtil,
+ private val pumpSync: PumpSync
+) : EoBaseViewModel() {
+ private val _eventHandler = SingleLiveEvent>()
+ val eventHandler : LiveData>
+ get() = _eventHandler
+
+ private val _patchConfig = SingleLiveEvent()
+ val patchConfig : LiveData
+ get() = _patchConfig
+
+ private val _patchState = SingleLiveEvent()
+ val patchState : LiveData
+ get() = _patchState
+
+ private val _normalBasal = SingleLiveEvent()
+ val normalBasal : LiveData