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 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 startQuickBolus(float nowDoseU, float exDoseU, BolusExDuration exDuration); + + Single 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 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 resumeBasal() { + return patchManager.resumeBasal(); + } + + + public Single 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 startQuickBolus(float nowDoseU, float exDoseU, + BolusExDuration exDuration) { + return patchManager.startQuickBolus(nowDoseU, exDoseU, exDuration); + } + + + public Single 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 resumeBasal() { + return resumeBasalTask.resume() + .timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS); + } + + public Single 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 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 startQuickBolus(float nowDoseU, float exDoseU, + BolusExDuration exDuration) { + return startQuickBolusTask.start(nowDoseU, exDoseU, exDuration) + .timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS); + } + + public Single 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 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 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 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 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 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 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 + get() = _normalBasal + + private val _tempBasal = SingleLiveEvent() + val tempBasal : LiveData + get() = _tempBasal + + private val _bleStatus = SingleLiveEvent() + val bleStatus : LiveData + get() = _bleStatus + + private val _status = SingleLiveEvent() + val status : LiveData + get() = _status + + private val _pauseBtnStr = SingleLiveEvent() + val pauseBtnStr : LiveData + get() = _pauseBtnStr + + private val _alarms = SingleLiveEvent() + val alarms : LiveData + get() = _alarms + + private val _patchRemainingInsulin = MutableLiveData(0f) + + private var mPauseTimeDisposable: Disposable? = null + private var mBasalRateDisposable: Disposable? = null + + val patchRemainingInsulin: LiveData + get() = Transformations.map(_patchRemainingInsulin) { insulin -> + when { + insulin > 50f -> "50+ U" + insulin < 1f -> "0 U" + else -> "${insulin.roundToInt()} U" + } + } + + val isPatchConnected: Boolean + get() = patchManager.patchConnectionState.isConnected + + init { + preferenceManager.observePatchConfig() + .observeOn(aapsSchedulers.main) + .subscribe { _patchConfig.value = it } + .addTo() + + preferenceManager.observePatchState() + .observeOn(aapsSchedulers.main) + .subscribe { + _patchState.value = it + _patchRemainingInsulin.value = it.remainedInsulin + updateBasalInfo() + updatePatchStatus() + } + .addTo() + + patchManager.observePatchConnectionState() + .observeOn(aapsSchedulers.main) + .subscribe { + _bleStatus.value = when(it){ + BleConnectionState.CONNECTED -> "{fa-bluetooth}" + BleConnectionState.DISCONNECTED -> "{fa-bluetooth-b}" + else -> "{fa-bluetooth-b spin} ${rh.gs(R.string.string_connecting)}" + } + } + .addTo() + + patchManager.observePatchLifeCycle() + .observeOn(aapsSchedulers.main) + .subscribe { + updatePatchStatus() + } + .addTo() + + preferenceManager.observeAlarm() + .observeOn(aapsSchedulers.main) + .subscribe { + _alarms.value = it + } + .addTo() + + if(preferenceManager.getPatchState().isNormalBasalPaused){ + startPauseTimeUpdate() + }else { + updateBasalInfo() + } + } + + private fun updatePatchStatus(){ + if(patchManager.isActivated){ + val finishTimeMillis = patchConfig.value?.basalPauseFinishTimestamp?:System.currentTimeMillis() + val remainTimeMillis = max(finishTimeMillis - System.currentTimeMillis(), 0L) + val h = TimeUnit.MILLISECONDS.toHours(remainTimeMillis) + val m = TimeUnit.MILLISECONDS.toMinutes(remainTimeMillis - TimeUnit.HOURS.toMillis(h)) + _status.value = if(patchManager.patchState.isNormalBasalPaused) + "${rh.gs(R.string.string_suspended)}\n${rh.gs(R.string.string_temp_basal_remained_hhmm, h.toString(), m.toString())}" + else + rh.gs(R.string.string_running) + }else{ + _status.value = "" + } + _pauseBtnStr.value = if(patchManager.patchState.isNormalBasalPaused) rh.gs(R.string.string_resume) else rh.gs(R.string.string_suspend) + } + + private fun updateBasalInfo(){ + if(patchManager.isActivated){ + _normalBasal.value = if(patchManager.patchState.isNormalBasalRunning) + "${preferenceManager.getNormalBasalManager().normalBasal.currentSegmentDoseUnitPerHour} U/hr" + else + "" + _tempBasal.value = if(patchManager.patchState.isTempBasalActive) + "${preferenceManager.getTempBasalManager().startedBasal?.doseUnitPerHour} U/hr" + else + "" + + }else{ + _normalBasal.value = "" + _tempBasal.value = "" + } + } + + fun onClickActivation(){ + val profile = profileFunction.getProfile() + if(profile == null){ + _eventHandler.postValue(UIEvent(EventType.PROFILE_NOT_SET)) + }else{ + val basalValues = profile.getBasalValues() + var isValid = true + for(basalRate in basalValues){ + if(basalRate.value < 0.049999){ + _eventHandler.postValue(UIEvent(EventType.INVALID_BASAL_RATE)) + isValid = false + break + } + } + + if(isValid) { + patchManager.preferenceManager.getNormalBasalManager().setNormalBasal(profile) + patchManager.preferenceManager.flushNormalBasalManager() + + _eventHandler.postValue(UIEvent(EventType.ACTIVATION_CLICKED)) + } + } + } + + fun onClickDeactivation(){ + _eventHandler.postValue(UIEvent(EventType.DEACTIVATION_CLICKED)) + } + + fun onClickSuspendOrResume(){ + if(patchManager.patchState.isNormalBasalPaused) { + _eventHandler.postValue(UIEvent(EventType.RESUME_CLICKED)) + }else{ + _eventHandler.postValue(UIEvent(EventType.SUSPEND_CLICKED)) + } + } + + fun pauseBasal(pauseDurationHour: Float){ + patchManager.pauseBasal(pauseDurationHour) + .subscribeOn(aapsSchedulers.io) + .observeOn(aapsSchedulers.main) + .subscribe({ response -> + if (response.isSuccess) { + var result = pumpSync.syncTemporaryBasalWithPumpId( + timestamp = dateUtil.now(), + rate = 0.0, + duration = T.mins((pauseDurationHour * 60).toLong()).msecs(), + isAbsolute = true, + type = PumpSync.TemporaryBasalType.PUMP_SUSPEND, + pumpId = dateUtil.now(), + pumpType = PumpType.EOFLOW_EOPATCH2, + pumpSerial = patchManager.patchConfig.patchSerialNumber + ) + aapsLogger.debug(LTag.PUMP, "syncTemporaryBasalWithPumpId: Result: $result") + + UIEvent(EventType.PAUSE_BASAL_SUCCESS).let { _eventHandler.postValue(it) } + startPauseTimeUpdate() + } else { + UIEvent(EventType.PAUSE_BASAL_FAILED).apply { value = pauseDurationHour }.let { _eventHandler.postValue(it) } + } + }, { + UIEvent(EventType.PAUSE_BASAL_FAILED).apply { value = pauseDurationHour }.let { _eventHandler.postValue(it) } + }).addTo() + } + + fun resumeBasal() { + patchManager.resumeBasal() + .subscribeOn(aapsSchedulers.io) + .observeOn(aapsSchedulers.main) + .subscribe({ + if (it.isSuccess) { + pumpSync.syncStopTemporaryBasalWithPumpId( + timestamp = dateUtil.now(), + endPumpId = dateUtil.now(), + pumpType = PumpType.EOFLOW_EOPATCH2, + pumpSerial = patchManager.patchConfig.patchSerialNumber + ) + UIEvent(EventType.RESUME_BASAL_SUCCESS).let { _eventHandler.postValue(it) } + stopPauseTimeUpdate() + } else { + _eventHandler.postValue(UIEvent(EventType.RESUME_BASAL_FAILED)) + } + },{ + _eventHandler.postValue(UIEvent(EventType.RESUME_BASAL_FAILED)) + }).addTo() + } + + private fun startPauseTimeUpdate(){ + if(mPauseTimeDisposable == null) { + mPauseTimeDisposable = Observable.interval(30, TimeUnit.SECONDS) + .observeOn(aapsSchedulers.main) + .subscribe { updatePatchStatus() } + } + } + + private fun stopPauseTimeUpdate(){ + mPauseTimeDisposable?.dispose() + mPauseTimeDisposable = null + } + + fun startBasalRateUpdate(){ + val initialDelaySecs = Calendar.getInstance().let { c -> + (60 - c.get(Calendar.MINUTE) - 1) * 60 + (60 - c.get(Calendar.SECOND)) + } + if(mBasalRateDisposable == null) { + mBasalRateDisposable = Observable.interval(initialDelaySecs.toLong(), 3600L, TimeUnit.SECONDS) + .observeOn(aapsSchedulers.main) + .subscribe { updateBasalInfo() } + } + updateBasalInfo() + } + + fun stopBasalRateUpdate(){ + mBasalRateDisposable?.dispose() + mBasalRateDisposable = null + } +} \ No newline at end of file diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/viewmodel/EopatchViewModel.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/viewmodel/EopatchViewModel.kt new file mode 100644 index 0000000000..374aead3c5 --- /dev/null +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/viewmodel/EopatchViewModel.kt @@ -0,0 +1,798 @@ +package info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel + +import android.content.res.Resources +import androidx.annotation.StringRes +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Transformations +import info.nightscout.androidaps.interfaces.ResourceHelper +import info.nightscout.androidaps.plugins.pump.eopatch.CommonUtils +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.ble.IPatchManager +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.code.SettingKeys.Companion.EXPIRATION_REMINDERS +import info.nightscout.androidaps.plugins.pump.eopatch.core.define.IPatchConstant +import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.BleConnectionState +import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.PatchSelfTestResult.TEST_SUCCESS +import info.nightscout.androidaps.plugins.pump.eopatch.extension.getDiffDays +import info.nightscout.androidaps.plugins.pump.eopatch.extension.subscribeDefault +import info.nightscout.androidaps.plugins.pump.eopatch.extension.subscribeEmpty +import info.nightscout.androidaps.plugins.pump.eopatch.extension.takeOne +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.ui.viewmodel.EopatchViewModel.SetupStep.* +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.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.Maybe +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.disposables.Disposable +import io.reactivex.rxjava3.subjects.PublishSubject +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.roundToInt + +class EopatchViewModel @Inject constructor( + private val rh: ResourceHelper, + val patchManager: IPatchManager, + private val alarmRegistry: IAlarmRegistry, + private val aapsLogger: AAPSLogger, + private val aapsSchedulers: AapsSchedulers, + private val rxAction: RxAction, + private val sp: SP +) : EoBaseViewModel() { + companion object { + private const val MAX_ELAPSED_MILLIS_AFTER_EXPIRATION = -12L * 60 * 60 * 1000 + } + var forceDiscard = false + var connectionTryCnt = 0 + + val patchConfig: PatchConfig = patchManager.patchConfig + val patchState: PatchState = patchManager.patchState + + private val _isActivated = MutableLiveData(patchConfig.isActivated) + + private val _eventHandler = SingleLiveEvent>() + val eventHandler : LiveData> + get() = _eventHandler + + val patchStep = MutableLiveData() + + val isActivated = MutableLiveData(patchManager.isActivated) + val isBolusActive = patchManager.patchState.isBolusActive + val isConnected = patchManager.patchConnectionState.isConnected + + val patchRemainedInsulin: LiveData + get() = Transformations.map(_isActivated) { + it.takeOne(patchManager.patchState.remainedInsulin.let { insulin -> + when { + insulin > 50f -> 51 + insulin < 1f -> 0 + else -> insulin.roundToInt() + } + }, 0) + } + + private val _patchExpirationTimestamp = MutableLiveData(patchManager.patchExpiredTime) + + val patchRemainedDays: LiveData + get() = Transformations.map(_patchExpirationTimestamp) { + it.getDiffDays().toInt() + } + + val patchRemainedTime: LiveData + get() = Transformations.map(_patchExpirationTimestamp) { + it.diffTime(MAX_ELAPSED_MILLIS_AFTER_EXPIRATION) + } + + private val _title = MutableLiveData() + val title: LiveData + get() = _title + + private val _safetyCheckProgress = MutableLiveData(0) + val safetyCheckProgress: LiveData + get() = _safetyCheckProgress + + private val _isCommCheckFailed = MutableLiveData(false) + private val isCommCheckFailed: LiveData + get() = _isCommCheckFailed + + private val isBonded: Boolean + get() = !patchConfig.lifecycleEvent.isShutdown + + val commCheckCancelLabel: LiveData + get() = Transformations.map(patchStep) { + rh.gs(when (it) { + PatchStep.CONNECT_NEW -> { + isBonded.takeOne(R.string.cancel, R.string.patch_cancel_pairing) + } + PatchStep.SAFE_DEACTIVATION -> R.string.patch_forced_discard + else -> R.string.cancel + }) + } + + val programEnabledMessage: String + get() = rh.gs(R.string.patch_basal_schedule_desc_1,"기초1") + + private val _isDiscardedWithNotConn = MutableLiveData(false) + val isDiscardedWithNotConn: LiveData + get() = _isDiscardedWithNotConn + + private val isSubStepRunning: Boolean + get() = patchConfig.lifecycleEvent.isSubStepRunning + + private val initPatchStepIsSafeDeactivation: Boolean + get() = mInitPatchStep?.isSafeDeactivation ?: false + + private val initPatchStepIsCheckConnection: Boolean + get() = mInitPatchStep?.isCheckConnection ?: false + + private var mCommCheckDisposable: Disposable? = null + + private var mOnCommCheckSuccessListener: (() -> Unit)? = null + + private var mOnCommCheckCancelListener: (() -> Unit)? = null + + private var mOnCommCheckDiscardListener: (() -> Unit)? = null + + private var mInitPatchStep: PatchStep? = null + + private val mMaxRetryCount = 3 + + private var mRetryCount = 0 + + private var mUpdateDisposable: Disposable? = null + + private var mB012UpdateDisposable: Disposable? = null + + private val mB012UpdateSubject = PublishSubject.create() + + init { + mB012UpdateDisposable = mB012UpdateSubject.hide() + .throttleFirst(500, TimeUnit.MILLISECONDS) + .delay(100, TimeUnit.MILLISECONDS) + .filter { isSubStepRunning } + .observeOn(aapsSchedulers.main) + .flatMapMaybe { alarmRegistry.remove(AlarmCode.B012) } + .flatMapMaybe { alarmRegistry.add(AlarmCode.B012, TimeUnit.MINUTES.toMillis(3)) } + .subscribe() + + patchManager.observePatchLifeCycle() + .observeOn(aapsSchedulers.main) + .subscribe { + isActivated.value = patchManager.isActivated + } + .addTo() + } + + private fun Long.diffTime(maxElapsed: Long): String { + val current = System.currentTimeMillis() + + return abs((this - current).let { (it > maxElapsed).takeOne(it, maxElapsed) }).let { millis -> + val hours = TimeUnit.MILLISECONDS.toHours(millis) + val minutes = TimeUnit.MILLISECONDS.toMinutes(millis + - TimeUnit.HOURS.toMillis(hours)) + val seconds = TimeUnit.MILLISECONDS.toSeconds(millis + - TimeUnit.HOURS.toMillis(hours) - TimeUnit.MINUTES.toMillis(minutes)) + + (this < current).takeOne("- ", "") +String.format( + "%02d:%02d:%02d", hours % 24, minutes, seconds) + } + } + + fun updateExpirationTime() { + CommonUtils.dispose(mUpdateDisposable) + + mUpdateDisposable = Observable.interval(0, 1, TimeUnit.SECONDS) + .observeOn(aapsSchedulers.main) + .takeUntil { !patchConfig.isActivated } + .subscribeDefault(aapsLogger) { + _patchExpirationTimestamp.value = patchManager.patchExpiredTime + } + } + + @Synchronized + fun checkCommunication(onSuccessListener: () -> Unit, onCancelListener: (() -> Unit)? = null, + onDiscardListener: (() -> Unit)? = null, doPreCheck: Boolean = false) { + if (doPreCheck && patchManager.patchConnectionState.isConnected) { + onSuccessListener.invoke() + return + } + + mOnCommCheckSuccessListener = onSuccessListener + mOnCommCheckCancelListener = onCancelListener + mOnCommCheckDiscardListener = onDiscardListener + checkCommunicationInternal() + } + + fun retryCheckCommunication() { + updateIncompletePatchActivationReminder() + checkCommunicationInternal() + } + + private fun checkCommunicationInternal(timeout: Long = 8000) { + CommonUtils.dispose(mCommCheckDisposable) + + if(forceDiscard) + connectionTryCnt++ + + mCommCheckDisposable = if (isBonded) { + patchManager.observePatchConnectionState() + .timeout(timeout, TimeUnit.MILLISECONDS, + Observable.just(BleConnectionState.DISCONNECTED)) + .takeUntil { it == BleConnectionState.CONNECTED } + .last(BleConnectionState.DISCONNECTED) + .map { it == BleConnectionState.CONNECTED } + } else { + patchManager.scan(timeout) + .flatMap { + if (it.nearestDevice == null) + Single.error(Resources.NotFoundException()) + else + Single.just(true) + } + .retry(1) + } + .subscribeOn(aapsSchedulers.io) + .observeOn(aapsSchedulers.main) + .onErrorReturnItem(false) + .doOnSubscribe { showPatchCommCheckDialog() } + .doFinally { dismissPatchCommCheckDialog() } + .doOnError { aapsLogger.error(LTag.PUMP, it.message?:"Error") } + .subscribeDefault(aapsLogger) { + _isCommCheckFailed.value = !it + } + } + + private fun showPatchCommCheckDialog(defaultFailedCondition: Boolean = false, @StringRes title: Int = R.string.string_connecting) { + _isCommCheckFailed.postValue(defaultFailedCondition) + _eventHandler.postValue(UIEvent(EventType.SHOW_PATCH_COMM_DIALOG).apply { + value = title + }) + } + + fun dismissPatchCommCheckDialogInternal(doOnSuccessOrCancel: Boolean? = null) { + _eventHandler.postValue(UIEvent(EventType.DISMISS_PATCH_COMM_DIALOG)) + doOnSuccessOrCancel?.let { + if (it) { + mOnCommCheckSuccessListener?.invoke() + } else { + mOnCommCheckCancelListener?.invoke() + } + } + mOnCommCheckSuccessListener = null + mOnCommCheckCancelListener = null + } + + private fun dismissPatchCommCheckDialog() { + if (isCommCheckFailed.value == false) { + if (isBonded) { + _eventHandler.postValue(UIEvent(EventType.SHOW_BONDED_DIALOG)) + } else { + dismissPatchCommCheckDialogInternal(true) + // _eventHandler.postValue(Event(UserEvent.DISMISS_PATCH_COMM_DIALOG)) + } + } else { + // dismissPatchCommCheckDialogInternal(false) + _eventHandler.postValue(UIEvent(EventType.DISMISS_PATCH_COMM_DIALOG)) + _eventHandler.postValue(UIEvent(EventType.SHOW_PATCH_COMM_ERROR_DIALOG)) + } + } + + fun cancelPatchCommCheck() { + CommonUtils.dispose(mCommCheckDisposable) + updateIncompletePatchActivationReminder() + dismissPatchCommCheckDialogInternal(false) + } + + @Synchronized + private fun showProgressDialog(@StringRes label: Int) { + _eventHandler.postValue(UIEvent(EventType.SHOW_PATCH_COMM_DIALOG).apply { + value = label + }) + // if (mProgressDialog == null) { + // mProgressDialog = PatchProgressDialog() + // + // mProgressDialog?.let { + // navigator?.showDialog(it.apply { + // setLabel(label) + // }) + // } + // } + } + + @Synchronized + private fun dismissProgressDialog() { + _eventHandler.postValue(UIEvent(EventType.DISMISS_PATCH_COMM_DIALOG)) + // try { + // mProgressDialog?.dismiss() + // mProgressDialog = null + // navigator?.dismissProgressDialog() + // } catch (e: IllegalStateException) { } + } + + fun changePatch() { + _eventHandler.postValue(UIEvent(EventType.SHOW_CHANGE_PATCH_DIALOG)) + } + + fun discardPatchWithCommCheck() { + checkCommunication({ discardPatchInternal() }, doPreCheck = true) + } + + fun deactivatePatch(){ + if (patchManager.patchConnectionState.isConnected) { + deactivate(false) { + try { + moveStep(PatchStep.DISCARDED_FOR_CHANGE) + } catch (e: IllegalStateException) { + _eventHandler.postValue(UIEvent(EventType.FINISH_ACTIVITY)) + } + } + } else { + mOnCommCheckSuccessListener = { + deactivate(true) { + moveStep((PatchStep.DISCARDED_FOR_CHANGE)) + } + } + showPatchCommCheckDialog(true) + Single.timer(10, TimeUnit.SECONDS) + .doFinally{dismissPatchCommCheckDialog()} + .subscribe() + } + } + fun discardPatch() { + updateIncompletePatchActivationReminder() + discardPatchInternal() + } + + private fun discardPatchInternal() { + val isBolusActive = patchManager.preferenceManager.getPatchState().isBolusActive + + if (patchStep.value == PatchStep.SAFE_DEACTIVATION && !isBolusActive) { + deactivate(true) { + dismissPatchCommCheckDialogInternal() + moveStep(PatchStep.MANUALLY_TURNING_OFF_ALARM) + } + + return + } + + _eventHandler.postValue(UIEvent(EventType.SHOW_DISCARD_DIALOG)) + } + + fun onConfirm() { + when (patchStep.value) { + PatchStep.DISCARDED_FOR_CHANGE -> PatchStep.WAKE_UP + PatchStep.DISCARDED_FROM_ALARM -> PatchStep.FINISH + PatchStep.DISCARDED -> { + if (initPatchStepIsCheckConnection) { + mOnCommCheckDiscardListener?.invoke() /*?: navigator?.finish()*/ + mOnCommCheckDiscardListener = null + null + } else { + PatchStep.BACK_TO_HOME + } + } + PatchStep.MANUALLY_TURNING_OFF_ALARM -> { + initPatchStepIsSafeDeactivation.takeOne(PatchStep.DISCARDED_FOR_CHANGE, PatchStep.DISCARDED) + } + PatchStep.BASAL_SCHEDULE -> { + if (!patchManager.patchConnectionState.isConnected) { + checkCommunication({ moveStep(PatchStep.COMPLETE) }, { moveStep(PatchStep.BASAL_SCHEDULE) }) + null + } else { + PatchStep.COMPLETE + } + } + else -> null + }?.let { + moveStep(it) + } + } + + fun initPatchStep() { + when (patchStep.value) { + PatchStep.WAKE_UP -> { + setupStep.value = WAKE_UP_READY + } + PatchStep.SAFETY_CHECK -> { + setupStep.value = SAFETY_CHECK_READY + } + PatchStep.ROTATE_KNOB, + PatchStep.ROTATE_KNOB_NEEDLE_INSERTION_ERROR -> { + setupStep.value = NEEDLE_SENSING_READY + } + else -> Unit + } + } + + fun moveStep(newPatchStep: PatchStep) { + val oldPatchStep = patchStep.value + + if (oldPatchStep != newPatchStep) { + when (newPatchStep) { + PatchStep.REMOVE_NEEDLE_CAP -> PatchLifecycleEvent.createRemoveNeedleCap() + PatchStep.REMOVE_PROTECTION_TAPE -> PatchLifecycleEvent.createRemoveProtectionTape() + PatchStep.SAFETY_CHECK -> PatchLifecycleEvent.createSafetyCheck() + PatchStep.ROTATE_KNOB -> PatchLifecycleEvent.createRotateKnob() + PatchStep.WAKE_UP -> { + patchConfig.apply { + rotateKnobNeedleSensingError = false + } + PatchLifecycleEvent.createShutdown() + } + PatchStep.CANCEL -> { + if (!patchConfig.isActivated) { + PatchLifecycleEvent.createShutdown() + } else { + null + } + } + else -> null + }?.let { + patchManager.updatePatchLifeCycle(it) + } + } + + prepareStep(newPatchStep) + + aapsLogger.info(LTag.PUMP, "moveStep: $oldPatchStep -> $newPatchStep") + } + + fun initializePatchStep(step: PatchStep?, withAlarmHandle: Boolean = true) { + mInitPatchStep = prepareStep(step, withAlarmHandle) + dismissPatchCommCheckDialogInternal(false) + // dismissTextDialog() + } + + private fun prepareStep(step: PatchStep?, withAlarmHandle: Boolean = true): PatchStep { + (step ?: convertToPatchStep(patchConfig.lifecycleEvent.lifeCycle)).let { newStep -> + when (newStep) { + PatchStep.SAFE_DEACTIVATION -> R.string.string_discard_patch + PatchStep.DISCARDED, + PatchStep.DISCARDED_FROM_ALARM, + PatchStep.DISCARDED_FOR_CHANGE -> R.string.patch_discard_complete_title + PatchStep.MANUALLY_TURNING_OFF_ALARM -> R.string.patch_manually_turning_off_alarm_title + PatchStep.WAKE_UP, + PatchStep.CONNECT_NEW, + PatchStep.REMOVE_NEEDLE_CAP, + PatchStep.REMOVE_PROTECTION_TAPE, + PatchStep.SAFETY_CHECK, + PatchStep.ROTATE_KNOB, + PatchStep.ROTATE_KNOB_NEEDLE_INSERTION_ERROR, + PatchStep.BASAL_SCHEDULE -> R.string.string_activate_patch + PatchStep.SETTING_REMINDER_TIME -> R.string.patch_expiration_reminder_setting_title + else -> _title.value + }.let { + if (_title.value != it) { + _title.postValue(it) + } + } + + patchStep.postValue(newStep) + + if (withAlarmHandle) { + /* Alarm reset */ + when (newStep) { + PatchStep.REMOVE_NEEDLE_CAP, + PatchStep.REMOVE_PROTECTION_TAPE, PatchStep.SAFETY_CHECK, PatchStep.ROTATE_KNOB -> { + updateIncompletePatchActivationReminder(true) + } + PatchStep.COMPLETE, PatchStep.BASAL_SCHEDULE -> { + val now = System.currentTimeMillis() + val expireTimeStamp = patchConfig.expireTimestamp + val milllisBeforeExpiration = TimeUnit.HOURS.toMillis(sp.getInt(EXPIRATION_REMINDERS, 0).toLong()) + + Maybe.just(AlarmCode.B012) + .flatMap { alarmRegistry.remove(it) } + .flatMap { alarmRegistry.remove(AlarmCode.A020) } + .flatMap { alarmRegistry.add(AlarmCode.B000, expireTimeStamp - now - milllisBeforeExpiration) } + .flatMap { alarmRegistry.add(AlarmCode.B005, expireTimeStamp - now) } + .flatMap { alarmRegistry.add(AlarmCode.B006, expireTimeStamp - now + IPatchConstant.SERVICE_TIME_MILLI - TimeUnit.HOURS.toMillis(1)) } + .flatMap { alarmRegistry.add(AlarmCode.A003, expireTimeStamp - now + IPatchConstant.SERVICE_TIME_MILLI) } + .subscribe() + } + + PatchStep.ROTATE_KNOB_NEEDLE_INSERTION_ERROR -> { + patchConfig.apply { + rotateKnobNeedleSensingError = true + } + + updateIncompletePatchActivationReminder(true) + + } + + PatchStep.CANCEL -> { + alarmRegistry.remove(AlarmCode.B012).subscribe() + } + + else -> { + } + } + } + + return newStep + } + } + + private fun convertToPatchStep(lifecycle: PatchLifecycle) = when (lifecycle) { + PatchLifecycle.SHUTDOWN -> patchConfig.isDeactivated.takeOne( + PatchStep.WAKE_UP, PatchStep.SAFE_DEACTIVATION) + PatchLifecycle.BONDED -> PatchStep.CONNECT_NEW + PatchLifecycle.REMOVE_NEEDLE_CAP -> PatchStep.REMOVE_NEEDLE_CAP + PatchLifecycle.REMOVE_PROTECTION_TAPE -> PatchStep.REMOVE_PROTECTION_TAPE + PatchLifecycle.SAFETY_CHECK -> PatchStep.SAFETY_CHECK + PatchLifecycle.ROTATE_KNOB -> PatchStep.ROTATE_KNOB + PatchLifecycle.BASAL_SETTING -> PatchStep.ROTATE_KNOB + PatchLifecycle.ACTIVATED -> PatchStep.SAFE_DEACTIVATION + } + + private fun onClear() { + mOnCommCheckSuccessListener = null + mOnCommCheckCancelListener = null + mOnCommCheckDiscardListener = null + CommonUtils.dispose(mCommCheckDisposable) + CommonUtils.dispose(mUpdateDisposable) + CommonUtils.dispose(mB012UpdateDisposable) + } + + override fun onCleared() { + super.onCleared() + onClear() + } + + enum class SetupStep { + WAKE_UP_READY, + SCAN_STARTED, + SCAN_FAILED, + BONDING_STARTED, + BONDING_FAILED, + GET_PATCH_INFO_STARTED, + GET_PATCH_INFO_FAILED, + SELF_TEST_STARTED, + SELF_TEST_FAILED, + SAFETY_CHECK_READY, + SAFETY_CHECK_STARTED, + SAFETY_CHECK_FAILED, + NEEDLE_SENSING_READY, + NEEDLE_SENSING_STARTED, + NEEDLE_SENSING_FAILED, + ACTIVATION_STARTED, + ACTIVATION_FAILED + } + + // 셋업 단계, UI 변경이 아닌 BLE 로직을 위한 SetupStep + val setupStep = MutableLiveData() + + private fun updateSetupStep(newSetupStep: SetupStep) { + aapsLogger.info(LTag.PUMP, "curSetupStep: ${setupStep.value}, newSetupStep: $newSetupStep") + setupStep.postValue(newSetupStep) + } + + @Synchronized + fun deactivate(force: Boolean, onSuccessListener: () -> Unit) { + patchManager.deactivate(6000, force) + .doOnSubscribe { + showProgressDialog(force.takeOne(R.string.string_in_progress, R.string.string_changing)) + } + .doFinally { + dismissProgressDialog() + } + .subscribeDefault(aapsLogger) { status -> + if (status.isDeactivated) { + onSuccessListener.invoke() + } else { + rxAction.runOnMainThread({ + checkCommunication({ deactivate(false, onSuccessListener) }, + { _eventHandler.postValue(UIEvent(EventType.FINISH_ACTIVITY)) }) + }, 100) + } + } + .addTo() + } + + fun deactivateInDisconnected() { + mOnCommCheckSuccessListener = { + deactivate(false) { moveStep((PatchStep.DISCARDED_FOR_CHANGE)) } + } + showPatchCommCheckDialog(true, R.string.string_discard_patch) + } + + @Synchronized + fun retryScan() { + if (mRetryCount <= mMaxRetryCount) { + startScanInternal() + } else { + moveStep(PatchStep.WAKE_UP) + } + } + + @Synchronized + fun startScan() { + if (isBonded) { + getPatchInfo() + } else { + mRetryCount = 0 + + startScanInternal() + } + } + + private fun startScanInternal() { + patchManager.scan(5000) + .flatMap { + if (it.nearestDevice == null) + Single.error(Resources.NotFoundException()) + else + Single.just(it.nearestDevice) + } + .onErrorReturnItem("") + .doOnSubscribe { updateSetupStep(SCAN_STARTED) } + .subscribeDefault(aapsLogger) { + if (!it.isNullOrEmpty()) { + startBond(it) + } else { + updateSetupStep(SCAN_FAILED) + } + }.addTo() + } + + @Synchronized + private fun startBond(scannedMacAddress: String) { + aapsLogger.info(LTag.PUMP, "startBond: $scannedMacAddress") + + patchManager.startBond(scannedMacAddress) + .doOnSubscribe { updateSetupStep(BONDING_STARTED) } + .filter { result -> result } + .toSingle() // 실패시 에러 반환. + .doOnSuccess { patchManager.updatePatchLifeCycle(PatchLifecycleEvent.createBonded()) } + .doOnError { + if (it is TimeoutException) { + moveStep(PatchStep.WAKE_UP) + } else { + updateSetupStep(BONDING_FAILED) + } + } + .subscribeDefault(aapsLogger) { + if (it) { + getPatchInfo() + } else { + updateSetupStep(BONDING_FAILED) + } + }.addTo() + } + + @Synchronized + fun getPatchInfo(timeout: Long = 60000) { + patchManager.getPatchInfo(timeout) + .doOnSubscribe { updateSetupStep(GET_PATCH_INFO_STARTED) } + .onErrorReturnItem(false) + .subscribeDefault(aapsLogger) { + if (it) { + selfTest(delayMs = 1000) + } else { + updateSetupStep(GET_PATCH_INFO_FAILED) + } + }.addTo() + } + + @Synchronized + fun selfTest(timeout: Long = 20000, delayMs: Long = 0) { + rxAction.runOnMainThread({ + patchManager.selfTest(timeout) + .doOnSubscribe { updateSetupStep(SELF_TEST_STARTED) } + .map { it == TEST_SUCCESS } + .onErrorReturnItem(false) + .subscribeDefault(aapsLogger) { + if (it) { + moveStep(PatchStep.REMOVE_NEEDLE_CAP) + } else if (!patchManager.patchConnectionState.isConnected) { + updateSetupStep(SELF_TEST_FAILED) + } + }.addTo() + }, delayMs) + } + + @Synchronized + fun retrySafetyCheck() { + if (mRetryCount <= mMaxRetryCount) { + startSafetyCheckInternal() + } else { + moveStep(PatchStep.REMOVE_NEEDLE_CAP) + } + } + + @Synchronized + fun startSafetyCheck() { + mRetryCount = 0 + + startSafetyCheckInternal() + } + + private fun startSafetyCheckInternal() { + patchManager.startPriming(10000, 100) + .doOnSubscribe { + _safetyCheckProgress.postValue(0) + updateSetupStep(SAFETY_CHECK_STARTED) + } + .doOnNext { _safetyCheckProgress.postValue(it.toInt()) } + .doOnError { updateSetupStep(SAFETY_CHECK_FAILED) } + .doOnComplete { moveStep(PatchStep.ROTATE_KNOB) } + .subscribeEmpty() + .addTo() + } + + @Synchronized + fun startNeedleSensing() { + patchManager.checkNeedleSensing(20000) + .toObservable() + .debounce(500, TimeUnit.MILLISECONDS) + .doOnSubscribe { + showProgressDialog(R.string.string_connecting) + updateSetupStep(NEEDLE_SENSING_STARTED) + } + .onErrorReturnItem(false) + .subscribeDefault(aapsLogger) { + if (it) { + startActivation() + } else { + if (!patchManager.patchConnectionState.isConnected) { + updateSetupStep(NEEDLE_SENSING_FAILED) + } + dismissProgressDialog() + } + }.addTo() + } + + @Synchronized + fun startActivation() { + patchManager.patchActivation(20000) + .doOnSubscribe { + showProgressDialog(R.string.string_connecting) + updateSetupStep(ACTIVATION_STARTED) + } + .doFinally { dismissProgressDialog() } + .onErrorReturnItem(false) + .subscribeDefault(aapsLogger) { + if (it) { + moveStep(PatchStep.COMPLETE) + } else { + updateSetupStep(ACTIVATION_FAILED) + } + }.addTo() + } + + fun updateIncompletePatchActivationReminder(forced: Boolean = false) { + if (forced || isSubStepRunning) { + mB012UpdateSubject.onNext(Unit) + } + } + + // @Synchronized + // private fun createTextDialog(): TextDialog { + // dismissTextDialog() + // + // return TextDialog().apply { + // mCurrentTextDialog = this + // } + // } + + // @Synchronized + // private fun dismissTextDialog() { + // mCurrentTextDialog?.dismiss() + // mCurrentTextDialog = null + // } +} diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/viewmodel/ViewModelFactory.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/viewmodel/ViewModelFactory.kt new file mode 100644 index 0000000000..0a351591e1 --- /dev/null +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ui/viewmodel/ViewModelFactory.kt @@ -0,0 +1,40 @@ +package info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import dagger.MapKey +import javax.inject.Inject +import javax.inject.Provider +import javax.inject.Singleton +import kotlin.reflect.KClass + +@MustBeDocumented +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) +@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) +@MapKey +internal annotation class ViewModelKey(val value: KClass) + +@Singleton +class ViewModelFactory @Inject constructor(private val creators: Map, @JvmSuppressWildcards Provider>) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + var creator: Provider? = creators[modelClass] + if (creator == null) { + for ((key, value) in creators) { + if (modelClass.isAssignableFrom(key)) { + creator = value + break + } + } + } + if (creator == null) { + throw IllegalArgumentException("unknown model class $modelClass") + } + try { + @Suppress("UNCHECKED_CAST") + return creator.get() as T + } catch (e: Exception) { + throw IllegalStateException(e) + } + + } +} \ No newline at end of file diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/Alarms.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/Alarms.kt new file mode 100644 index 0000000000..58facc5ef2 --- /dev/null +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/Alarms.kt @@ -0,0 +1,99 @@ +package info.nightscout.androidaps.plugins.pump.eopatch.vo + +import info.nightscout.androidaps.plugins.pump.eopatch.GsonHelper +import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode +import info.nightscout.androidaps.plugins.pump.eopatch.code.SettingKeys +import info.nightscout.shared.sharedPreferences.SP +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.subjects.BehaviorSubject +import java.util.* + +class Alarms: IPreference { + @Transient + private val subject: BehaviorSubject = BehaviorSubject.create() + + class AlarmItem { + lateinit var alarmCode: AlarmCode + var createTimestamp = 0L + var triggerTimeMilli = 0L + + override fun toString(): String { + return "AlarmItem(alarmCode=$alarmCode, createTimestamp=$createTimestamp, triggerTimeMilli=$triggerTimeMilli)" + } + } + + var registered = HashMap() + + var occurred = HashMap() + + init { + initObject() + } + + fun initObject() { + } + + fun clear(){ + registered.clear() + occurred.clear() + } + + fun update(other: Alarms) { + registered = other.registered + occurred = other.occurred + } + + fun register(alarmCode: AlarmCode, triggerAfter: Long) { + val item = AlarmItem().apply { + this.alarmCode = alarmCode + createTimestamp = System.currentTimeMillis() + triggerTimeMilli = createTimestamp + triggerAfter + } + if (isRegistered(alarmCode)){ + registered.remove(alarmCode) + } + registered.put(alarmCode, item) + + } + + fun unregister(alarmCode: AlarmCode) { + if (isRegistered(alarmCode)){ + registered.remove(alarmCode) + } + } + + fun occurred(alarmCode: AlarmCode) { + val item: AlarmItem? = registered.get(alarmCode) + if (!isOccurring(alarmCode) && item != null) + occurred.put(alarmCode, item) + if (isRegistered(alarmCode)) + registered.remove(alarmCode) + } + + fun handle(alarmCode: AlarmCode) { + if (isOccurring(alarmCode)) + occurred.remove(alarmCode) + } + + private fun isRegistered(alarmCode: AlarmCode): Boolean{ + return registered.containsKey(alarmCode) + } + + fun isOccurring(alarmCode: AlarmCode): Boolean{ + return occurred.containsKey(alarmCode) + } + + override fun observe(): Observable { + return subject.hide() + } + + override fun flush(sp: SP){ + val jsonStr = GsonHelper.sharedGson().toJson(this) + sp.putString(SettingKeys.ALARMS, jsonStr) + subject.onNext(this) + } + + override fun toString(): String { + return "Alarms(subject=$subject, registered=${registered.keys}, occurred=${occurred.keys}" + } +} diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/BasalSegment.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/BasalSegment.kt new file mode 100644 index 0000000000..b7125d06af --- /dev/null +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/BasalSegment.kt @@ -0,0 +1,45 @@ +package info.nightscout.androidaps.plugins.pump.eopatch.vo + +import com.google.android.gms.common.internal.Preconditions +import info.nightscout.androidaps.plugins.pump.eopatch.AppConstant + +data class BasalSegment (var start: Long, var end: Long, var doseUnitPerHour: Float) : SegmentEntity() { + + + override val isEmpty: Boolean + get() = doseUnitPerHour == 0f + + init { + Preconditions.checkArgument(start >= 0 && end > 0 && start < end) + Preconditions.checkArgument(start % 30 == 0L && end % 30 == 0L) + Preconditions.checkArgument(doseUnitPerHour >= 0) + this.startMinute = start + this.endMinute = end + } + + override fun duplicate(startMinute: Long, endMinute: Long): BasalSegment { + return BasalSegment(startMinute, endMinute, doseUnitPerHour) + } + + override fun deep(): BasalSegment { + return BasalSegment(startMinute, endMinute, doseUnitPerHour) + } + + override fun apply(segment: JoinedSegment) { + segment.doseUnitPerHour = doseUnitPerHour + } + + override fun equalValue(segment: BasalSegment): Boolean { + return doseUnitPerHour == segment.doseUnitPerHour + } + + companion object { + fun create(startMinute: Long, endMinute: Long, doseUnitPerHour: Float): BasalSegment { + return BasalSegment(startMinute, endMinute, doseUnitPerHour) + } + + fun create(doseUnitPerHour: Float): BasalSegment { + return BasalSegment(AppConstant.DAY_START_MINUTE.toLong(), AppConstant.DAY_END_MINUTE.toLong(), doseUnitPerHour) + } + } +} diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/BolusCurrent.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/BolusCurrent.kt new file mode 100644 index 0000000000..b094b163a0 --- /dev/null +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/BolusCurrent.kt @@ -0,0 +1,181 @@ +package info.nightscout.androidaps.plugins.pump.eopatch.vo + +import info.nightscout.androidaps.plugins.pump.eopatch.AppConstant +import info.nightscout.androidaps.plugins.pump.eopatch.GsonHelper +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.code.SettingKeys +import info.nightscout.shared.sharedPreferences.SP +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.subjects.BehaviorSubject + +/** + * 볼루스 주입 형태 2가지 모드 + * + * + * Bolus : '즉시 주입 볼루스' 와 '연장 주입 볼루스' 를 포함한 의미. + * Now Bolus : '즉시 주입 볼루스' 를 의미. + * Extended Bolus : '연장 주입 볼루스' 를 의미. + * + * + * BolusCurrent : 현재 패치에서 진행 중인 '볼루스의 정보'를 표현한 클래스. + */ + +class BolusCurrent(): IPreference { + @Transient + private val subject: BehaviorSubject = BehaviorSubject.create() + + class Bolus { + var historyId: Long = 0L + + var injected = 0f + + var remain = 0f + + var startTimestamp = 0L + + var endTimestamp = 0L + + // 즉시 주입 볼루스의 종료시간을 보정했는지 여부 + var endTimeSynced = false + + var duration = 0L + + val totalDoseU: Float + get() = injected + remain + + fun startBolus(id: Long, targetDoseU: Float, start: Long, end: Long, duration: Long = 0L) { + this.historyId = id + this.injected = 0f + this.remain = targetDoseU // 남은 양에 설정한다 + this.startTimestamp = start + this.endTimestamp = end + this.endTimeSynced = false + this.duration = duration + } + + fun clearBolus() { + this.historyId = 0 + this.injected = 0f + this.remain = 0f + this.startTimestamp = 0 + this.endTimestamp = 0 + this.endTimeSynced = false + this.duration = 0L + } + + fun update(injected: Int, remain: Int) { + this.injected = FloatAdjusters.FLOOR2_BOLUS.apply(injected * AppConstant.INSULIN_UNIT_P) + this.remain = FloatAdjusters.FLOOR2_BOLUS.apply(remain * AppConstant.INSULIN_UNIT_P) + } + + fun updateTimeStamp(start: Long, end: Long) { + this.startTimestamp = start + this.endTimestamp = end + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Bolus + + if (historyId != other.historyId) return false + if (injected != other.injected) return false + if (remain != other.remain) return false + if (startTimestamp != other.startTimestamp) return false + if (endTimestamp != other.endTimestamp) return false + if (endTimeSynced != other.endTimeSynced) return false + return true + } + + override fun hashCode(): Int { + var result = historyId.hashCode() + result = 31 * result + injected.hashCode() + result = 31 * result + remain.hashCode() + result = 31 * result + startTimestamp.hashCode() + result = 31 * result + endTimestamp.hashCode() + result = 31 * result + endTimeSynced.hashCode() + return result + } + + override fun toString(): String = + when (historyId) { + 0L -> "Bolus(NONE)" + else -> "Bolus(id=$historyId, i=$injected, r=$remain, start=$startTimestamp, end=$endTimestamp, synced=$endTimeSynced)" + } + } + + var nowBolus: Bolus = Bolus() + var extBolus: Bolus = Bolus() + + private fun getBolus(type: BolusType): Bolus = + when (type) { + BolusType.NOW -> nowBolus + BolusType.EXT -> extBolus + else -> nowBolus + } + + fun historyId(t: BolusType) = getBolus(t).historyId + fun injected(t: BolusType) = getBolus(t).injected + fun remain(t: BolusType) = getBolus(t).remain + fun startTimestamp(t: BolusType) = getBolus(t).startTimestamp + fun endTimestamp(t: BolusType) = getBolus(t).endTimestamp + fun endTimeSynced(t: BolusType) = getBolus(t).endTimeSynced + fun totalDoseU(t: BolusType) = getBolus(t).totalDoseU + fun duration(t: BolusType) = getBolus(t).duration + + fun clearBolus(t: BolusType) = getBolus(t).clearBolus() + + fun clearAll() { + clearBolus(BolusType.NOW) + clearBolus(BolusType.EXT) + } + + fun setEndTimeSynced(t: BolusType, synced: Boolean) { + getBolus(t).endTimeSynced = synced + } + + fun startNowBolus(nowHistoryId: Long, targetDoseU: Float, startTimestamp: Long, endTimestamp: Long) { + nowBolus.startBolus(nowHistoryId, targetDoseU, startTimestamp, endTimestamp) + } + + fun startExtBolus(exHistoryId: Long, targetDoseU: Float, startTimestamp: Long, endTimestamp: Long, duration: Long) { + extBolus.startBolus(exHistoryId, targetDoseU, startTimestamp, endTimestamp, duration) + } + + fun updateBolusFromPatch(type: BolusType, injected: Int, remain: Int) { + getBolus(type).update(injected, remain) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as BolusCurrent + + if (nowBolus != other.nowBolus) return false + if (extBolus != other.extBolus) return false + return true + } + + override fun observe(): Observable { + return subject.hide() + } + + override fun flush(sp: SP){ + val jsonStr = GsonHelper.sharedGson().toJson(this) + sp.putString(SettingKeys.BOLUS_CURRENT, jsonStr) + subject.onNext(this) + } + + override fun hashCode(): Int { + var result = nowBolus.hashCode() + result = 31 * result + extBolus.hashCode() + return result + } + + override fun toString(): String { + return "BolusCurrent(nowBolus=$nowBolus, extBolus=$extBolus)" + } +} diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/IPreference.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/IPreference.kt new file mode 100644 index 0000000000..ccb254b79f --- /dev/null +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/IPreference.kt @@ -0,0 +1,9 @@ +package info.nightscout.androidaps.plugins.pump.eopatch.vo + +import info.nightscout.shared.sharedPreferences.SP +import io.reactivex.rxjava3.core.Observable + +interface IPreference{ + fun flush(sp: SP) + fun observe(): Observable +} \ No newline at end of file diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/NormalBasal.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/NormalBasal.kt new file mode 100644 index 0000000000..17a8f9d426 --- /dev/null +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/NormalBasal.kt @@ -0,0 +1,190 @@ +package info.nightscout.androidaps.plugins.pump.eopatch.vo + +import info.nightscout.androidaps.plugins.pump.eopatch.AppConstant +import info.nightscout.androidaps.plugins.pump.eopatch.core.util.FloatAdjusters +import info.nightscout.androidaps.plugins.pump.eopatch.code.BasalStatus +import java.util.* +import java.util.concurrent.TimeUnit +import kotlin.math.max + +class NormalBasal : SegmentsEntity() { + var status: BasalStatus = BasalStatus.SELECTED + @Synchronized + set(status) { + field = status + if (status == BasalStatus.STOPPED) { + this.segmentIndex = SEGMENT_INDEX_DEFAULT + } + } + + private var segmentIndex = SEGMENT_INDEX_DEFAULT + + val maxDoseUnitPerHour: Float + get() { + val max = list.stream().map { it.doseUnitPerHour }.mapToDouble { it.toDouble() }.max().orElse(0.0).toFloat() + return FloatAdjusters.ROUND2_INSULIN.apply(max) + } + + val doseUnitPerSegmentArray: FloatArray + get() { + val doseArray = FloatArray(AppConstant.SEGMENT_COUNT_MAX) + + eachSegmentItem { index, segment -> + val dose = segment.doseUnitPerHour / 2 + if (index % 2 == 0) { + doseArray[index] = FloatAdjusters.CEIL2_BASAL_RATE.apply(dose) + } else { + doseArray[index] = FloatAdjusters.FLOOR2_BASAL_RATE.apply(dose) + } + true + } + return doseArray + } + + private val doseUnitPerSegmentArrayForGraph: FloatArray + get() { + val doseArray = FloatArray(AppConstant.SEGMENT_COUNT_MAX) + + eachSegmentItem { index, segment -> + doseArray[index] = FloatAdjusters.CEIL2_BASAL_RATE.apply(segment.doseUnitPerHour) + true + } + return doseArray + } + + val doseUnitPerDay: Float + get() { + var total = 0f + eachSegmentItem { index, segment -> + val dose = segment.doseUnitPerHour / 2 + total += if (index % 2 == 0) { + FloatAdjusters.CEIL2_BASAL_RATE.apply(dose) + } else { + FloatAdjusters.FLOOR2_BASAL_RATE.apply(dose) + } + true + } + return total + } + + val currentSegmentDoseUnitPerHour: Float + get() = FloatAdjusters.ROUND2_INSULIN.apply(getSegmentDoseUnitPerHourByIndex(currentSegmentIndex)) + + private val currentSegmentIndex: Int + get() { + val cal = Calendar.getInstance() + var idx = cal.get(Calendar.HOUR_OF_DAY) * 2 + if (cal.get(Calendar.MINUTE) >= 30) { + idx += 1 + } + return idx + } + + val startTime: Long + get() = getStartTime(currentSegmentIndex) + + init { + initObject() + } + + fun initObject() { + status = BasalStatus.SELECTED + list.add(BasalSegment.create(AppConstant.BASAL_RATE_PER_HOUR_MIN)) + } + + fun getSegmentDoseUnitPerHour(time: Long): Float { + return FloatAdjusters.ROUND2_INSULIN.apply(getSegmentDoseUnitPerHourByIndex(getSegmentIndex(time))) + } + + private fun getSegmentDoseUnitPerHourByIndex(idx: Int): Float { + val defaultValue = 0f + for (seg in list) { + val startIndex = seg.startIndex + val endIndex = seg.endIndex + if (startIndex <= idx && idx < endIndex) { + return seg.doseUnitPerHour + } + } + return defaultValue + } + + private fun getSegmentIndex(time: Long): Int { + val cal = Calendar.getInstance() + cal.timeInMillis = time + var idx = cal.get(Calendar.HOUR_OF_DAY) * 2 + if (cal.get(Calendar.MINUTE) >= 30) { + idx += 1 + } + return idx + } + + fun getMaxBasal(durationMinutes: Long): Float { + val calendar = Calendar.getInstance() + calendar.timeInMillis = System.currentTimeMillis() + var hours = calendar.get(Calendar.HOUR_OF_DAY) + var minutes = calendar.get(Calendar.MINUTE) + + var startIndex = hours * 2 + minutes / 30 + startIndex %= AppConstant.SEGMENT_COUNT_MAX + + hours = (hours + durationMinutes / 60).toInt() + minutes = (minutes + durationMinutes % 60).toInt() + + var endIndex = hours * 2 + minutes / 30 + endIndex %= AppConstant.SEGMENT_COUNT_MAX + + val segments = doseUnitPerSegmentArrayForGraph + var maxBasal = segments[startIndex] + var i = startIndex + while (i != endIndex + 1) { + if (i >= AppConstant.SEGMENT_COUNT_MAX) { + i %= AppConstant.SEGMENT_COUNT_MAX + } + maxBasal = max(maxBasal, segments[i]) + i++ + } + return maxBasal + } + + @Synchronized + fun updateNormalBasalIndex(): Boolean { + val currentSegmentIndex = currentSegmentIndex + if (segmentIndex != SEGMENT_INDEX_DEFAULT) { + if (segmentIndex != currentSegmentIndex) { + segmentIndex = currentSegmentIndex + return true + } + } else { + segmentIndex = currentSegmentIndex + return true + } + return false + } + + private fun getStartTime(segmentIndex: Int): Long { + val curIndexTime: Long + val calendar = Calendar.getInstance() + calendar.set(Calendar.HOUR_OF_DAY, 0) + calendar.set(Calendar.MINUTE, 0) + calendar.set(Calendar.SECOND, 0) + calendar.set(Calendar.MILLISECOND, 0) + + curIndexTime = calendar.timeInMillis + TimeUnit.MINUTES.toMillis((segmentIndex * 30).toLong()) + return curIndexTime + } + + override fun toString(): String { + return "NormalBasal(status=$status, segmentIndex=$segmentIndex, list=$list)" + } + + companion object { + private const val SEGMENT_INDEX_DEFAULT = -1 + + fun create(firstSegmentDoseUnitPerHour: Float): NormalBasal { + val b = NormalBasal() + b.status = BasalStatus.SELECTED + b.list[0].doseUnitPerHour = firstSegmentDoseUnitPerHour + return b + } + } +} diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/NormalBasalManager.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/NormalBasalManager.kt new file mode 100644 index 0000000000..fe85271de9 --- /dev/null +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/NormalBasalManager.kt @@ -0,0 +1,127 @@ +package info.nightscout.androidaps.plugins.pump.eopatch.vo + +import info.nightscout.androidaps.interfaces.Profile +import info.nightscout.androidaps.plugins.pump.eopatch.CommonUtils +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.BasalStatus +import info.nightscout.shared.sharedPreferences.SP +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.subjects.BehaviorSubject +import java.util.concurrent.TimeUnit + +class NormalBasalManager : IPreference { + @Transient + private val subject: BehaviorSubject = BehaviorSubject.create() + + var normalBasal: NormalBasal = NormalBasal() + + val isStarted: Boolean + get() = normalBasal.status.isStarted + + + init { + initObject() + } + + + fun initObject() { + } + + fun isEqual(profile: Profile?): Boolean{ + return profile?.let{ + if(it.getBasalValues().size != normalBasal.list.size) + return false + + for(i in it.getBasalValues().indices){ + if(TimeUnit.SECONDS.toMinutes(it.getBasalValues()[i].timeAsSeconds.toLong()) != normalBasal.list.get(i).start){ + return false + } + if(!CommonUtils.nearlyEqual(it.getBasalValues()[i].value.toFloat(), normalBasal + .list.get(i).doseUnitPerHour, 0.0000001f)){ + return false + } + } + return true + }?:false + } + + fun convertProfileToNormalBasal(profile: Profile): NormalBasal { + val tmpNormalBasal = NormalBasal() + tmpNormalBasal.list.clear() + + val size = profile.getBasalValues().size + for(idx in profile.getBasalValues().indices){ + val nextIdx = if(idx == (size - 1)) 0 else idx + 1 + val startTimeMinutes = TimeUnit.SECONDS.toMinutes(profile.getBasalValues()[idx].timeAsSeconds.toLong()) + val endTimeMinutes = if(nextIdx == 0) 1440 else TimeUnit.SECONDS.toMinutes(profile.getBasalValues()[nextIdx].timeAsSeconds.toLong()) + + tmpNormalBasal.list.add(BasalSegment(startTimeMinutes, endTimeMinutes, profile.getBasalValues()[idx].value.toFloat())) + } + + return tmpNormalBasal + } + + fun setNormalBasal(profile: Profile) { + normalBasal.list.clear() + + val size = profile.getBasalValues().size + for(idx in profile.getBasalValues().indices){ + val nextIdx = if(idx == (size - 1)) 0 else idx + 1 + val startTimeMinutes = TimeUnit.SECONDS.toMinutes(profile.getBasalValues()[idx].timeAsSeconds.toLong()) + val endTimeMinutes = if(nextIdx == 0) 1440 else TimeUnit.SECONDS.toMinutes(profile.getBasalValues()[nextIdx].timeAsSeconds.toLong()) + + normalBasal.list.add(BasalSegment(startTimeMinutes, endTimeMinutes, profile.getBasalValues()[idx].value.toFloat())) + } + } + + @Synchronized + fun updateBasalStarted() { + normalBasal.status = BasalStatus.STARTED + } + + @Synchronized + fun updateBasalPaused() { + normalBasal.status = BasalStatus.PAUSED + } + + @Synchronized + fun updateBasalSuspended() { + normalBasal.status = BasalStatus.SUSPENDED + } + + @Synchronized + fun isSuspended(): Boolean { + return normalBasal.status == BasalStatus.SUSPENDED + } + + @Synchronized + fun updateBasalSelected() { + normalBasal.status = BasalStatus.SELECTED + } + + fun updateForDeactivation() { + // deactivation 할때는 SELECTED 상태로 변경 + updateBasalSelected() + } + + fun update(other: NormalBasalManager){ + normalBasal = other.normalBasal + } + + override fun observe(): Observable { + return subject.hide() + } + + override fun flush(sp: SP){ + val jsonStr = GsonHelper.sharedGson().toJson(this) + sp.putString(SettingKeys.NORMAL_BASAL, jsonStr) + subject.onNext(this) + } + + + override fun toString(): String { + return "NormalBasalManager(normalBasal=$normalBasal)" + } + +} diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/PatchConfig.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/PatchConfig.kt new file mode 100644 index 0000000000..caa522392e --- /dev/null +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/PatchConfig.kt @@ -0,0 +1,399 @@ +package info.nightscout.androidaps.plugins.pump.eopatch.vo + +import com.google.android.gms.common.internal.Preconditions +import info.nightscout.androidaps.plugins.pump.eopatch.AppConstant +import info.nightscout.androidaps.plugins.pump.eopatch.CommonUtils +import info.nightscout.androidaps.plugins.pump.eopatch.FloatFormatters +import info.nightscout.androidaps.plugins.pump.eopatch.GsonHelper +import info.nightscout.androidaps.plugins.pump.eopatch.core.define.IPatchConstant.WARRANTY_OPERATING_LIFE_MILLI +import info.nightscout.androidaps.plugins.pump.eopatch.code.SettingKeys +import info.nightscout.androidaps.plugins.pump.eopatch.code.PatchLifecycle +import info.nightscout.shared.sharedPreferences.SP +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.subjects.BehaviorSubject +import java.util.concurrent.TimeUnit + +// @Singleton +class PatchConfig: IPreference { + @Transient + private val subject: BehaviorSubject = BehaviorSubject.create() + var securityValue: ByteArray = byteArrayOf(0, 0) + + var macAddress: String? = null + var lifecycleEvent: PatchLifecycleEvent = PatchLifecycleEvent() + var bolusNormalStartTimestamp = 0L + var bolusNormalEndTimestamp = 0L + var bolusNormalDoseU = 0f + var bolusExStartTimestamp = 0L + var bolusExEndTimestamp = 0L + var bolusExDoseU = 0f + var injectCount = 0 + var bgReminderMinute = 0L + var lastIndex = 0 + var lastDisconnectedTimestamp = 0L // 마지막 연결 종료 시간 + + var standardBolusInjectCount = 0 + var extendedBolusInjectCount = 0 + var basalInjectCount = 0 + + var patchFirmwareVersion: String? = null + var patchSerialNumber: String = "" + var patchLotNumber: String? = null + var patchModelName: String? = null + + var patchWakeupTimestamp = 0L + set(wakeupTimestamp) { + field = wakeupTimestamp + expireDurationMilli = WARRANTY_OPERATING_LIFE_MILLI + } + + var activatedTimestamp = 0L // 최초 연결 시간 + var expireDurationMilli = 0L // 패치 만료 기간 , 3일 (wake-up 시간을 기준으로 3일) + var basalPauseFinishTimestamp = 0L // 베이젤 일시중지 만료 시간 + var needleInsertionTryCount = 0 // 바늘삽입 시도 횟수 + + /* 패치와 API 통신으로 업데이트 값을 여기에 기록 중복 API 호출이 생기면 안되는 경우 여기에 */ + // SET_LOW_RESERVOIR_TASK + var lowReservoirAlertAmount = 10 + var patchExpireAlertTime = 4 + var infoReminder = false + + var pumpDurationSmallMilli = 0L // small + get(): Long = if (field != 0L) field else AppConstant.PUMP_DURATION_MILLI + var pumpDurationMediumMilli = 0L // medium + get(): Long = if (field != 0L) field else AppConstant.PUMP_DURATION_MILLI + var pumpDurationLargeMilli = 0L // large + get(): Long = if (field != 0L) field else AppConstant.PUMP_DURATION_MILLI + //var pumpDurationOcclusion = 0L // occul, 사용안함 + + var isEnterPrimaryScreen =false + // 기초 프로그램 변경시 BLE로 패치에 보내야 하기 때문에 마크한다. + var needSetBasalSchedule =false + + var sharedKey: ByteArray? = null + var seq15: Int = -1 + + var rotateKnobNeedleSensingError = false + + var remainedInsulin = 0f + //wake-up 시간을 기준으로 3.5일 + val expireTimestamp: Long + get() = patchWakeupTimestamp + expireDurationMilli + + val isExpired: Boolean + get() = System.currentTimeMillis() >= expireTimestamp + + val isActivated: Boolean + get() = this.lifecycleEvent.isActivated + + val isSubStepRunning: Boolean + get() = this.lifecycleEvent.isSubStepRunning + + val isBasalSetting: Boolean + get() = this.lifecycleEvent.isBasalSetting + + val isDeactivated: Boolean + get() = !hasMacAddress() + + val isInBasalPausedTime: Boolean + get() = this.basalPauseFinishTimestamp > 0 && basalPauseFinishTimestamp > System.currentTimeMillis() + + val insulinInjectionAmount: Float + get() = injectCount * AppConstant.INSULIN_UNIT_P + + val insulinInjectionAmountStr: String + get() = FloatFormatters.insulin(injectCount * AppConstant.INSULIN_UNIT_P, "U") + + val bolusInjectionAmount: Float + get() = (standardBolusInjectCount + extendedBolusInjectCount) * AppConstant.INSULIN_UNIT_P + + val basalInjectionAmount: Float + get() = basalInjectCount * AppConstant.INSULIN_UNIT_P + + init { + initObject() + } + + fun initObject() { + this.lifecycleEvent = PatchLifecycleEvent() + this.lastIndex = 0 + } + + fun updateDeactivated() { + this.macAddress = null + this.patchFirmwareVersion = null + this.patchSerialNumber = "" + this.patchLotNumber = null + this.patchWakeupTimestamp = 0 + this.activatedTimestamp = 0 + this.expireDurationMilli = 0 + this.lifecycleEvent = PatchLifecycleEvent() + this.needleInsertionTryCount = 0 + this.bolusNormalStartTimestamp = 0 + this.bolusNormalEndTimestamp = 0 + this.bolusExStartTimestamp = 0 + this.bolusExEndTimestamp = 0 + this.injectCount = 0 + this.lastIndex = 0 + this.pumpDurationSmallMilli = 0 + this.pumpDurationMediumMilli = 0 + this.pumpDurationLargeMilli = 0 + this.needSetBasalSchedule = false + this.sharedKey = null + this.seq15 = -1 + this.standardBolusInjectCount = 0 + this.extendedBolusInjectCount = 0 + this.basalInjectCount = 0 + this.lowReservoirAlertAmount = 10 + this.patchExpireAlertTime = 4 + this.remainedInsulin = 0f + } + + fun patchFirmwareVersionString(): String? { + patchFirmwareVersion?.let { + var count = 0 + var i = 0 + while (i < it.length) { + if (it[i] == '.') { + count++ + if (count == 3) { + return it.substring(0, i) + } + } + i++ + } + } + + return patchFirmwareVersion + } + + fun getPatchExpiredTime(): Long = + if (isActivated) expireTimestamp else -1L + + @Synchronized + fun incSeq() { + if (seq15 >= 0) { + seq15++ + } + if (seq15 > 0x7FFF) { + seq15 = 0 + } + } + + fun updateLifecycle(event: PatchLifecycleEvent) { + Preconditions.checkNotNull(event) + + /* 마지막 이력을 기록해 두어야 알람이 재발생하는 것을 막아야 함 */ + if (event.lifeCycle == lifecycleEvent.lifeCycle) { + return + } + + this.lifecycleEvent = event + when (event.lifeCycle) { + PatchLifecycle.SHUTDOWN -> { + updateDeactivated() + } + + PatchLifecycle.BONDED -> { + } + + PatchLifecycle.SAFETY_CHECK -> { + } + + PatchLifecycle.REMOVE_NEEDLE_CAP -> { + } + + PatchLifecycle.REMOVE_PROTECTION_TAPE -> { + } + + PatchLifecycle.ROTATE_KNOB -> { + } + + PatchLifecycle.BASAL_SETTING -> { + } + + PatchLifecycle.ACTIVATED -> { + // updateFirstConnected 이 부분으로 옮김. + this.activatedTimestamp = System.currentTimeMillis() + //this.expireDurationMilli = WARRANTY_OPERATING_LIFE_MILLI + //this.patchWakeupTimestamp = 0 getwakeuptime response 로 업데이트 됨. + this.needleInsertionTryCount = 0 + this.bolusNormalStartTimestamp = 0 + this.bolusNormalEndTimestamp = 0 + this.bolusExStartTimestamp = 0 + this.bolusExEndTimestamp = 0 + this.lastIndex = 0 + this.needSetBasalSchedule = false + } + } + } + + fun updateNormalBasalPaused(pauseDurationHour: Float) { + Preconditions.checkArgument(pauseDurationHour == 0.5f || pauseDurationHour == 1.0f || pauseDurationHour == 1.5f || pauseDurationHour == 2.0f) + this.basalPauseFinishTimestamp = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis((pauseDurationHour * 60).toLong()) + } + + fun updateNormalBasalPausedSilently() { + this.basalPauseFinishTimestamp = 0L + } + + fun updateNormalBasalResumed() { + this.basalPauseFinishTimestamp = 0 + } + + fun updateNormalBasalStarted() { + this.basalPauseFinishTimestamp = 0 + } + + fun updateTempBasalStarted() { + this.basalPauseFinishTimestamp = 0 + } + + fun hasMacAddress(): Boolean { + return CommonUtils.hasText(macAddress) + } + + + fun updatetDisconnectedTime(){ + this.lastDisconnectedTimestamp = System.currentTimeMillis() + } + + fun update(other: PatchConfig){ + macAddress = other.macAddress + lifecycleEvent = other.lifecycleEvent + bolusNormalStartTimestamp = other.bolusNormalStartTimestamp + bolusNormalEndTimestamp = other.bolusNormalEndTimestamp + bolusNormalDoseU = other.bolusNormalDoseU + bolusExStartTimestamp = other.bolusExStartTimestamp + bolusExEndTimestamp = other.bolusExEndTimestamp + bolusExDoseU = other.bolusExDoseU + bgReminderMinute = other.bgReminderMinute + lastIndex = other.lastIndex + lastDisconnectedTimestamp = other.lastDisconnectedTimestamp + patchFirmwareVersion = other.patchFirmwareVersion + patchSerialNumber = other.patchSerialNumber + patchLotNumber = other.patchLotNumber + patchWakeupTimestamp = other.patchWakeupTimestamp + activatedTimestamp = other.activatedTimestamp + expireDurationMilli = other.expireDurationMilli + basalPauseFinishTimestamp = other.basalPauseFinishTimestamp + needleInsertionTryCount = other.needleInsertionTryCount + isEnterPrimaryScreen = other.isEnterPrimaryScreen + needSetBasalSchedule = other.needSetBasalSchedule + sharedKey = other.sharedKey + seq15 = other.seq15 + patchModelName = other.patchModelName + needleInsertionTryCount = other.needleInsertionTryCount + injectCount = other.injectCount + pumpDurationSmallMilli = other.pumpDurationSmallMilli + pumpDurationMediumMilli = other.pumpDurationMediumMilli + pumpDurationLargeMilli = other.pumpDurationLargeMilli + standardBolusInjectCount = other.standardBolusInjectCount + extendedBolusInjectCount = other.extendedBolusInjectCount + basalInjectCount = other.basalInjectCount + lowReservoirAlertAmount = other.lowReservoirAlertAmount + patchExpireAlertTime = other.patchExpireAlertTime + remainedInsulin = other.remainedInsulin + + subject.onNext(this) + } + + override fun observe(): Observable { + return subject.hide() + } + + override fun flush(sp: SP){ + val jsonStr = GsonHelper.sharedGson().toJson(this) + sp.putString(SettingKeys.PATCH_CONFIG, jsonStr) + subject.onNext(this) + } + + + + override fun toString(): String { + return "PatchConfig(securityValue=${securityValue.contentToString()}, macAddress=$macAddress, lifecycleEvent=$lifecycleEvent, bolusNormalStartTimestamp=$bolusNormalStartTimestamp, bolusNormalEndTimestamp=$bolusNormalEndTimestamp, bolusNormalDoseU=$bolusNormalDoseU, bolusExStartTimestamp=$bolusExStartTimestamp, bolusExEndTimestamp=$bolusExEndTimestamp, bolusExDoseU=$bolusExDoseU, injectCount=$injectCount, bgReminderMinute=$bgReminderMinute, lastIndex=$lastIndex, lastDisconnectedTimestamp=$lastDisconnectedTimestamp, standardBolusInjectCount=$standardBolusInjectCount, extendedBolusInjectCount=$extendedBolusInjectCount, basalInjectCount=$basalInjectCount, patchFirmwareVersion=$patchFirmwareVersion, patchSerialNumber='$patchSerialNumber', patchLotNumber=$patchLotNumber, patchModelName=$patchModelName, patchWakeupTimestamp=$patchWakeupTimestamp, activatedTimestamp=$activatedTimestamp, expireDurationMilli=$expireDurationMilli, basalPauseFinishTimestamp=$basalPauseFinishTimestamp, needleInsertionTryCount=$needleInsertionTryCount, LowReservoirAlertAmount=$lowReservoirAlertAmount, patchExpireAlertTime=$patchExpireAlertTime, isEnterPrimaryScreen=$isEnterPrimaryScreen, needSetBasalSchedule=$needSetBasalSchedule, sharedKey=${sharedKey?.contentToString()}, seq15=$seq15, rotateKnobNeedleSensingError=$rotateKnobNeedleSensingError, remainedInsulin=$remainedInsulin)" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as PatchConfig + + if (!securityValue.contentEquals(other.securityValue)) return false + if (macAddress != other.macAddress) return false + if (lifecycleEvent != other.lifecycleEvent) return false + if (bolusNormalStartTimestamp != other.bolusNormalStartTimestamp) return false + if (bolusNormalEndTimestamp != other.bolusNormalEndTimestamp) return false + if (bolusNormalDoseU != other.bolusNormalDoseU) return false + if (bolusExStartTimestamp != other.bolusExStartTimestamp) return false + if (bolusExEndTimestamp != other.bolusExEndTimestamp) return false + if (bolusExDoseU != other.bolusExDoseU) return false + if (injectCount != other.injectCount) return false + if (bgReminderMinute != other.bgReminderMinute) return false + if (lastIndex != other.lastIndex) return false + if (lastDisconnectedTimestamp != other.lastDisconnectedTimestamp) return false + if (standardBolusInjectCount != other.standardBolusInjectCount) return false + if (extendedBolusInjectCount != other.extendedBolusInjectCount) return false + if (basalInjectCount != other.basalInjectCount) return false + if (patchFirmwareVersion != other.patchFirmwareVersion) return false + if (patchSerialNumber != other.patchSerialNumber) return false + if (patchLotNumber != other.patchLotNumber) return false + if (patchModelName != other.patchModelName) return false + if (patchWakeupTimestamp != other.patchWakeupTimestamp) return false + if (activatedTimestamp != other.activatedTimestamp) return false + if (expireDurationMilli != other.expireDurationMilli) return false + if (basalPauseFinishTimestamp != other.basalPauseFinishTimestamp) return false + if (needleInsertionTryCount != other.needleInsertionTryCount) return false + if (lowReservoirAlertAmount != other.lowReservoirAlertAmount) return false + if (patchExpireAlertTime != other.patchExpireAlertTime) return false + if (isEnterPrimaryScreen != other.isEnterPrimaryScreen) return false + if (needSetBasalSchedule != other.needSetBasalSchedule) return false + if (sharedKey != null) { + if (other.sharedKey == null) return false + if (!sharedKey.contentEquals(other.sharedKey)) return false + } else if (other.sharedKey != null) return false + if (seq15 != other.seq15) return false + if (rotateKnobNeedleSensingError != other.rotateKnobNeedleSensingError) return false + if (remainedInsulin != other.remainedInsulin) return false + + return true + } + + override fun hashCode(): Int { + var result = securityValue.contentHashCode() + result = 31 * result + (macAddress?.hashCode() ?: 0) + result = 31 * result + lifecycleEvent.hashCode() + result = 31 * result + bolusNormalStartTimestamp.hashCode() + result = 31 * result + bolusNormalEndTimestamp.hashCode() + result = 31 * result + bolusNormalDoseU.hashCode() + result = 31 * result + bolusExStartTimestamp.hashCode() + result = 31 * result + bolusExEndTimestamp.hashCode() + result = 31 * result + bolusExDoseU.hashCode() + result = 31 * result + injectCount + result = 31 * result + bgReminderMinute.hashCode() + result = 31 * result + lastIndex + result = 31 * result + lastDisconnectedTimestamp.hashCode() + result = 31 * result + standardBolusInjectCount + result = 31 * result + extendedBolusInjectCount + result = 31 * result + basalInjectCount + result = 31 * result + (patchFirmwareVersion?.hashCode() ?: 0) + result = 31 * result + patchSerialNumber.hashCode() + result = 31 * result + (patchLotNumber?.hashCode() ?: 0) + result = 31 * result + (patchModelName?.hashCode() ?: 0) + result = 31 * result + patchWakeupTimestamp.hashCode() + result = 31 * result + activatedTimestamp.hashCode() + result = 31 * result + expireDurationMilli.hashCode() + result = 31 * result + basalPauseFinishTimestamp.hashCode() + result = 31 * result + needleInsertionTryCount + result = 31 * result + lowReservoirAlertAmount + result = 31 * result + patchExpireAlertTime + result = 31 * result + isEnterPrimaryScreen.hashCode() + result = 31 * result + needSetBasalSchedule.hashCode() + result = 31 * result + (sharedKey?.contentHashCode() ?: 0) + result = 31 * result + seq15 + result = 31 * result + rotateKnobNeedleSensingError.hashCode() + result = 31 * result + remainedInsulin.hashCode() + return result + } +} diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/PatchLifecycleEvent.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/PatchLifecycleEvent.kt new file mode 100644 index 0000000000..eb0f0be6eb --- /dev/null +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/PatchLifecycleEvent.kt @@ -0,0 +1,96 @@ +package info.nightscout.androidaps.plugins.pump.eopatch.vo + +import info.nightscout.androidaps.plugins.pump.eopatch.code.PatchLifecycle +import com.google.android.gms.common.internal.Preconditions + +class PatchLifecycleEvent { + + var lifeCycle: PatchLifecycle = PatchLifecycle.SHUTDOWN + + var timestamp = 0L + + val isSafetyCheck: Boolean + get() = this.lifeCycle == PatchLifecycle.SAFETY_CHECK + + val isBasalSetting: Boolean + get() = this.lifeCycle == PatchLifecycle.BASAL_SETTING + + val isSubStepRunning: Boolean + get() = this.lifeCycle.rawValue > PatchLifecycle.SHUTDOWN.rawValue && this.lifeCycle.rawValue < PatchLifecycle.ACTIVATED.rawValue + + val isRotateKnob: Boolean + get() = this.lifeCycle == PatchLifecycle.ROTATE_KNOB + + val isShutdown: Boolean + get() = this.lifeCycle.rawValue == PatchLifecycle.SHUTDOWN.rawValue + + val isActivated: Boolean + get() = this.lifeCycle == PatchLifecycle.ACTIVATED + + constructor() { + initObject() + } + + fun initObject() { + this.lifeCycle = PatchLifecycle.SHUTDOWN + this.timestamp = System.currentTimeMillis() + } + + override fun toString(): String { + return "PatchLifecycleEvent(lifeCycle=$lifeCycle, timestamp=$timestamp)" + } + + constructor(lifeCycle: PatchLifecycle) { + Preconditions.checkNotNull(lifeCycle) + this.lifeCycle = lifeCycle + this.timestamp = System.currentTimeMillis() + } + + companion object { + fun create(lifecycle: PatchLifecycle): PatchLifecycleEvent { + Preconditions.checkNotNull(lifecycle) + + return PatchLifecycleEvent(lifecycle) + } + + @JvmStatic + fun createShutdown(): PatchLifecycleEvent { + return PatchLifecycleEvent(PatchLifecycle.SHUTDOWN) + } + + @JvmStatic + fun createBonded(): PatchLifecycleEvent { + return PatchLifecycleEvent(PatchLifecycle.BONDED) + } + + @JvmStatic + fun createRemoveNeedleCap(): PatchLifecycleEvent { + return PatchLifecycleEvent(PatchLifecycle.REMOVE_NEEDLE_CAP) + } + + @JvmStatic + fun createRemoveProtectionTape(): PatchLifecycleEvent { + return PatchLifecycleEvent(PatchLifecycle.REMOVE_PROTECTION_TAPE) + } + + @JvmStatic + fun createSafetyCheck(): PatchLifecycleEvent { + return PatchLifecycleEvent(PatchLifecycle.SAFETY_CHECK) + } + + @JvmStatic + fun createRotateKnob(): PatchLifecycleEvent { + return PatchLifecycleEvent(PatchLifecycle.ROTATE_KNOB) + } + + @JvmStatic + fun createBasalSetting(): PatchLifecycleEvent { + return PatchLifecycleEvent(PatchLifecycle.BASAL_SETTING) + } + + @JvmStatic + fun createActivated(): PatchLifecycleEvent { + return PatchLifecycleEvent(PatchLifecycle.ACTIVATED) + } + } +} diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/PatchState.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/PatchState.kt new file mode 100644 index 0000000000..f40e3c732d --- /dev/null +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/PatchState.kt @@ -0,0 +1,354 @@ +package info.nightscout.androidaps.plugins.pump.eopatch.vo + +import info.nightscout.androidaps.plugins.pump.eopatch.AppConstant +import info.nightscout.androidaps.plugins.pump.eopatch.GsonHelper +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.code.SettingKeys +import info.nightscout.shared.sharedPreferences.SP +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.subjects.BehaviorSubject +import java.io.Serializable +import java.text.SimpleDateFormat +import java.util.* +import java.util.concurrent.TimeUnit +import java.util.function.Consumer +import java.util.stream.IntStream + +class PatchState: IPreference { + @Transient + private val subject: BehaviorSubject = BehaviorSubject.create() + + private val stateBytes: ByteArray + var updatedTimestamp: Long = 0 + + constructor(): this(ByteArray(SIZE), 0) { + } + + constructor(stateBytes: ByteArray, updatedTimestamp: Long) { + this.stateBytes = stateBytes + this.updatedTimestamp = updatedTimestamp + } + + fun update(newValue: ByteArray, timestamp: Long) { + if (newValue.size == 17) { + stateBytes[D0] = 0x00 + stateBytes[D1] = 0x00 + stateBytes[D2] = 0x22 + System.arraycopy(newValue, 0, stateBytes, 3, 17) + } else { + System.arraycopy(newValue, 0, stateBytes, 0, stateBytes.size) + } + updatedTimestamp = timestamp + subject.onNext(this) + } + + fun clear(){ + update(ByteArray(SIZE), 0) + } + + fun update(other: PatchState) { + if (other.stateBytes.size == 17) { + stateBytes[D0] = 0x00 + stateBytes[D1] = 0x00 + stateBytes[D2] = 0x22 + System.arraycopy(other.stateBytes, 0, stateBytes, 3, 17) + } else { + System.arraycopy(other.stateBytes, 0, stateBytes, 0, stateBytes.size) + } + updatedTimestamp = other.updatedTimestamp + subject.onNext(this) + } + + val isEmpty: Boolean + get() = updatedTimestamp == 0L + + private fun get(index: Int): Int { + return stateBytes[index].toInt() and 0xFF + } + + private fun getBoolean(index: Int, bit: Int): Boolean { + return bitwiseAnd(stateBytes[index], bit) != 0 + } + + private fun bitwiseAnd(value: Byte, bit: Int): Int { + return value.toInt() and (1 shl bit) + } + + val isNeedPriming: Boolean + get() = getBoolean(D3, 1) + val isNeedNeedleSensing: Boolean + get() = getBoolean(D3, 2) + + val isPrimingSuccess: Boolean + get() = getBoolean(D3, 6) + + fun primingState(): Int { + return stateBytes[D3].toInt() and 0x70 shr 4 + } + + val isNowBolusRegAct: Boolean + get() = getBoolean(D4, 0) + val isExtBolusRegAct: Boolean + get() = getBoolean(D4, 1) + val isNormalBasalReg: Boolean + get() = getBoolean(D4, 2) + val isTempBasalReg: Boolean + get() = getBoolean(D4, 3) + val isNormalBasalAct: Boolean + get() = getBoolean(D4, 4) + val isTempBasalAct: Boolean + get() = getBoolean(D4, 5) + + fun isExtBolusInjecting(): Boolean { + return getBoolean(D4, 6) + } + + val isNewAlertAlarm: Boolean + get() = getBoolean(D5, 0) + val isCriticalAlarm: Boolean + get() = getBoolean(D5, 7) + + val isPumpAct: Boolean + get() = getBoolean(D6, 0) + val isPatchInternalSuspended: Boolean + get() = getBoolean(D6, 2) + val isNowBolusDone: Boolean + get() = getBoolean(D6, 4) + + val isExtBolusTime: Boolean + get() = getBoolean(D6, 5) + val isExtBolusDone: Boolean + get() = getBoolean(D6, 6) + val isTempBasalDone: Boolean + get() = getBoolean(D6, 7) + + fun battery(): String { + return String.format("%.2fV", (get(D7) + 145) / 100.0f) + } + + fun batteryLevel(): Int { + val volt = (get(D7) + 145) * 10 + val batteryLevel: Int + if (volt >= 3000) { + batteryLevel = 100 + } else if (volt > 2900) { + batteryLevel = 100 - ((3000 - volt) * 10) / 100 + } else if (volt > 2740) { + batteryLevel = 90 - ((2900 - volt) * 20) / 160 + } else if (volt > 2440) { + batteryLevel = 70 - ((2740 - volt) * 50) / 300 + } else if (volt > 2100) { + batteryLevel = 20 - ((2440 - volt) * 20) / 340 + } else { + batteryLevel = 0 + } + + return batteryLevel + } + + //============================================================================================== + // PUMP COUNT + //============================================================================================== + private fun remainedPumpCycle(): Int { + return stateBytes[D12].toInt() and 0xFF shl 8 or (stateBytes[D12 + 1].toInt() and 0xFF) + } + + //============================================================================================== + // CURRENT TIME (TimeUnit.SECOND) + //============================================================================================== + fun currentTime(): Int { + return byteToInt(stateBytes, D14) + } + + //============================================================================================== + // REMAINED INSULIN + //============================================================================================== + private fun remainedInsulin(): Int { + return get(D18) + } + + val remainedInsulin: Float + get() { + val remainedPumpCycle = remainedPumpCycle() + return if (remainedPumpCycle > 0) { + FloatAdjusters.FLOOR2_INSULIN.apply( + remainedPumpCycle * AppConstant.INSULIN_UNIT_P) + } else { + remainedInsulin().toFloat() + } + } + + //============================================================================================== + // RUNNING TIME + //============================================================================================== + fun runningTime(): Int { + return get(D19) + } + + //============================================================================================== + // Helper methods + //============================================================================================== + val isNormalBasalPaused: Boolean + get() = isNormalBasalReg && !isNormalBasalAct + val isNormalBasalRunning: Boolean + get() = isNormalBasalReg && isNormalBasalAct + + val isTempBasalActive: Boolean + get() = isTempBasalReg && isTempBasalAct && !isTempBasalDone + + /* + Bolus + */ + val isBolusActive: Boolean + get() = isNowBolusActive || isExtBolusActive + val isNowBolusActive: Boolean + get() = isNowBolusRegAct && !isNowBolusDone + val isNowBolusFinished: Boolean + get() = isNowBolusRegAct && isNowBolusDone + val isExtBolusActive: Boolean + get() = isExtBolusRegAct && !isExtBolusDone + val isExtBolusFinished: Boolean + get() = isExtBolusRegAct && isExtBolusDone + + fun isBolusActive(type: BolusType?): Boolean { + return when (type) { + BolusType.NOW -> isNowBolusRegAct + BolusType.EXT -> isExtBolusRegAct + BolusType.COMBO -> isNowBolusRegAct && isExtBolusRegAct + else -> isNowBolusRegAct && isExtBolusRegAct + } + } + + fun isBolusDone(type: BolusType?): Boolean { + return when (type) { + BolusType.NOW -> isNowBolusDone + BolusType.EXT -> isExtBolusDone + BolusType.COMBO -> isNowBolusDone || isExtBolusDone + else -> isNowBolusDone || isExtBolusDone + } + } + + private fun b(value: Boolean): String { + return if (value) ON else " " + } + + fun t(): String { + return "PatchState{" + convertHumanTimeWithStandard(currentTime()) + "}" + } + + override fun toString(): String { + val sb = StringBuilder() + val indent = " " + sb.append("PatchState") + if (isCriticalAlarm || isNewAlertAlarm) { + sb.append(indent).append("#### Error:") + .append(if (isCriticalAlarm) "Critical" else "") + .append(if (isNewAlertAlarm) "Alert" else "") + } + sb.append(indent).append("GlobalTime:").append(convertHumanTimeWithStandard(currentTime())) + sb.append(" --> ") + IntStream.of(D3, D4, D5, D6, D7, D8, D9, D10, D11, D12, D13, D18) + .forEach { i: Int -> sb.append(String.format(" %02X ", stateBytes[i])) } + if (isPatchInternalSuspended) { + listOf(indent, "isPatchInternalSuspended:", ON).forEach(Consumer { str: String? -> sb.append(str) }) + } + if (isNeedPriming) { + listOf(indent, "NeedPriming:", ON).forEach(Consumer { str: String? -> sb.append(str) }) + } + if (isNeedNeedleSensing) { + listOf(indent, "NeedNeedleSensing:", ON).forEach(Consumer { str: String? -> sb.append(str) }) + } + if (isNowBolusRegAct || isNowBolusDone) { + listOf(indent, "[NowBolus] RegAct:", b(isNowBolusRegAct), " Done:", b(isNowBolusDone)) + .forEach(Consumer { str: String? -> sb.append(str) }) + } + if (isExtBolusRegAct || isExtBolusDone || isExtBolusTime || isExtBolusInjecting()) { + listOf(indent, "[ExtBolus] RegAct:", b(isExtBolusRegAct), " Done:", b(isExtBolusDone), " Time:", b(isExtBolusTime), " Injecting:", b(isExtBolusInjecting())) + .forEach(Consumer { str: String? -> sb.append(str) }) + } + if (isTempBasalReg || isTempBasalAct || isTempBasalDone) { + listOf(indent, "[TempBasal] Reg:", b(isTempBasalReg), " Act:", b(isTempBasalAct), " Done:", b(isTempBasalDone)) + .forEach(Consumer { str: String? -> sb.append(str) }) + } + listOf(indent, "[NormalBasal] Reg:", b(isNormalBasalReg), " Act:", b(isNormalBasalAct), " Paused:", b(isNormalBasalPaused), + indent, "remainedInsulin:", remainedInsulin(), " remainedPumpCycle:", remainedPumpCycle(), "(", remainedInsulin, ")", " battery:", battery()) + .forEach(Consumer { obj: Serializable? -> sb.append(obj) }) + return sb.toString() + } + + fun convertHumanTimeWithStandard(timeSec: Int): String { + val calendar = Calendar.getInstance() + val dateFormat = SimpleDateFormat("yyyy/MM/dd HH:mm:ss") + calendar.timeInMillis = TimeUnit.SECONDS.toMillis(timeSec.toLong()) + return dateFormat.format(calendar.time) + } + + fun equalState(other: PatchState): Boolean { + return IntStream.of(D3, D4, D5, D6, D7, D8, D9, D10, D11, D12, D13, D18, D19) + .allMatch { i: Int -> other.stateBytes[i] == stateBytes[i] } + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null || javaClass != other.javaClass) { + return false + } + val that = other as PatchState + return stateBytes.contentEquals(that.stateBytes) + } + + override fun observe(): Observable { + return subject.hide() + } + + override fun flush(sp: SP){ + val jsonStr = GsonHelper.sharedGson().toJson(this) + sp.putString(SettingKeys.PATCH_STATE, jsonStr) + subject.onNext(this) + } + + override fun hashCode(): Int { + return stateBytes.contentHashCode() + } + + companion object { + const val SIZE = 20 + @JvmStatic fun create(bytes: ByteArray?, updatedTimestamp: Long): PatchState { + var stateBytes = bytes + if (stateBytes == null || stateBytes.size < SIZE) { + stateBytes = ByteArray(SIZE) + } + stateBytes[D0] = 0x00 + stateBytes[D1] = 0x00 + stateBytes[D2] = 0x22 + return PatchState(stateBytes, updatedTimestamp) + } + + private const val ON = "On" + + private fun byteToInt(bs: ByteArray, startPos: Int): Int { + return bs[startPos + 0].toInt() and 0xFF shl 24 or (bs[startPos + 1].toInt() and 0xFF shl 16) or (bs[startPos + 2].toInt() and 0xFF shl 8) or (bs[startPos + 3].toInt() and 0xFF) + } + + private const val D0 = 0 + private const val D1 = 1 + private const val D2 = 2 + private const val D3 = 3 + private const val D4 = D3 + 1 + private const val D5 = D3 + 2 + private const val D6 = D3 + 3 + private const val D7 = D3 + 4 + private const val D8 = D3 + 5 + private const val D9 = D3 + 6 + private const val D10 = D3 + 7 + private const val D11 = D3 + 8 + private const val D12 = D3 + 9 + private const val D13 = D3 + 10 + private const val D14 = D3 + 11 + private const val D18 = D3 + 15 + private const val D19 = D3 + 16 + } +} \ No newline at end of file diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/Segment.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/Segment.kt new file mode 100644 index 0000000000..804f3266f1 --- /dev/null +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/Segment.kt @@ -0,0 +1,18 @@ +package info.nightscout.androidaps.plugins.pump.eopatch.vo + +internal class JoinedSegment(var index: Int) { + + var no = 0 + + var startMinute = 0 + var endMinute = 0 + + + //BasalSegment + var doseUnitPerHour = 0f + + init { + startMinute = index * SegmentEntity.TIME_BASE + endMinute = startMinute + SegmentEntity.TIME_BASE + } +} diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/SegmentEntity.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/SegmentEntity.kt new file mode 100644 index 0000000000..4ba422df82 --- /dev/null +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/SegmentEntity.kt @@ -0,0 +1,94 @@ +package info.nightscout.androidaps.plugins.pump.eopatch.vo + +import java.util.ArrayList + +abstract class SegmentEntity> { + var startMinute = 0L + var endMinute = 0L + + internal val startIndex: Int + get() = (startMinute / TIME_BASE).toInt() + + internal val endIndex: Int + get() = (endMinute / TIME_BASE).toInt() + + fun getDuration(): Long { + return endMinute - startMinute + } + + internal abstract val isEmpty: Boolean + + internal fun isMinuteIncluding(minute: Long): Boolean { + return startMinute <= minute && minute < endMinute + } + + internal fun isSame(target: SegmentEntity<*>): Boolean { + return startMinute == target.startMinute && target.endMinute == endMinute + } + + internal fun hasSame(target: SegmentEntity<*>): Boolean { + return startMinute == target.startMinute || target.endMinute == endMinute + } + + internal fun canCover(target: SegmentEntity<*>): Boolean { + return startMinute <= target.startMinute && target.endMinute <= endMinute + } + + internal fun isCoveredBy(target: SegmentEntity<*>): Boolean { + return target.canCover(this) + } + + internal fun isOverlapped(target: SegmentEntity<*>): Boolean { + return startMinute < target.endMinute && target.startMinute < endMinute + } + + internal fun isPartiallyNotFullyIncluding(target: SegmentEntity<*>): Boolean { + return isOverlapped(target) && !canCover(target) && !isCoveredBy(target) + } + + internal fun subtract(target: SegmentEntity<*>, validCheck: Boolean) { + if (validCheck) { + if (!isPartiallyNotFullyIncluding(target)) { + return + } + } + + if (target.startMinute <= startMinute) { + startMinute = target.endMinute + } else if (endMinute <= target.endMinute) { + endMinute = target.startMinute + } + } + + internal fun splitBy(target: T, validCheck: Boolean): List? { + if (validCheck) { + if (!canCover(target)) { + return null + } + } + + val result = ArrayList() + + if (startMinute < target.startMinute) { + result.add(duplicate(startMinute, target.startMinute)) + } + + if (target.endMinute < endMinute) { + result.add(duplicate(target.endMinute, endMinute)) + } + return result + } + + internal fun includes(segment: JoinedSegment): Boolean { + return startMinute <= segment.startMinute && segment.endMinute <= endMinute + } + + internal abstract fun duplicate(startMinute: Long, endMinute: Long): T + internal abstract fun deep(): T + internal abstract fun equalValue(segment: T): Boolean + internal abstract fun apply(segment: JoinedSegment) + + companion object { + const val TIME_BASE = 30 + } +} diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/SegmentsEntity.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/SegmentsEntity.kt new file mode 100644 index 0000000000..64775d316f --- /dev/null +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/SegmentsEntity.kt @@ -0,0 +1,117 @@ +package info.nightscout.androidaps.plugins.pump.eopatch.vo + +import info.nightscout.androidaps.plugins.pump.eopatch.AppConstant +import java.util.* +import java.util.function.BiFunction + +abstract class SegmentsEntity> { + var list: ArrayList = ArrayList() + + val segmentCount: Int + get() = list.size + + val copiedSegmentList: ArrayList + get() = ArrayList(list) + + val deepCopiedSegmentList: ArrayList + get() { + val copied = ArrayList() + + for (seg in list) { + copied.add(seg.deep()) + } + + return copied + } + + private val timeMinute: Long + get() { + val c = Calendar.getInstance() + val hour = c.get(Calendar.HOUR_OF_DAY) + val min = c.get(Calendar.MINUTE) + + val segmentIndex = (hour * 60 + min) / 30 + + return (segmentIndex * 30).toLong() + } + + fun hasSegments(): Boolean { + return list.isNotEmpty() + } + + fun eachSegmentItem(eachFunc: BiFunction) { + for (seg in list) { + val startIndex = seg.startIndex + val endIndex = seg.endIndex + for (i in startIndex until endIndex) { + val shouldContinue = eachFunc.apply(i, seg) + if (!shouldContinue) { + break + } + } + } + } + + fun isValid(allowEmpty: Boolean): Boolean { + if (!allowEmpty) { + if (list.isEmpty()) { + return false + } + } + + for (seg in list) { + if (seg.isEmpty) { + return false + } + } + return true + } + + private fun getSegment(minute: Long): T? { + for (seg in list) { + if (seg.isMinuteIncluding(minute)) { + return seg + } + } + + return null + } + + fun getCurrentSegment(): T? { + return getSegment(timeMinute) + } + + fun isFullSegment(): Boolean { + var start = 0L + val end = 1440L + for(seg in list){ + if(seg.startMinute == start){ + start = seg.endMinute + }else{ + return false + } + } + return start == end + } + + fun getEmptySegment(): Pair { + if(list.isNullOrEmpty()) { + return Pair(0, AppConstant.SEGMENT_COUNT_MAX) + } + if(list[0].startIndex != 0) { + return Pair(0, list[0].startIndex) + } + + if(list.size == 1) { + return Pair(list[0].endIndex, AppConstant.SEGMENT_COUNT_MAX) + } + + for(i in 0 until list.size-1) { + if(list[i].endIndex != list[i+1].startIndex) { + return Pair(list[i].endIndex, list[i+1].startIndex) + } + } + + return Pair(list[list.size-1].endIndex, 48) + } +} diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/TempBasal.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/TempBasal.kt new file mode 100644 index 0000000000..d1b2ab3b50 --- /dev/null +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/TempBasal.kt @@ -0,0 +1,91 @@ +package info.nightscout.androidaps.plugins.pump.eopatch.vo + +import info.nightscout.androidaps.plugins.pump.eopatch.CommonUtils +import info.nightscout.androidaps.plugins.pump.eopatch.FloatFormatters +import info.nightscout.androidaps.plugins.pump.eopatch.core.util.FloatAdjusters +import info.nightscout.androidaps.plugins.pump.eopatch.code.UnitOrPercent +import java.util.concurrent.TimeUnit + +class TempBasal { + var startTimestamp = 0L + var durationMinutes: Long = 0L + var doseUnitPerHour: Float = -1f + var percent: Int = 0 + var unitDefinition: UnitOrPercent? = null + + var running =false + + val percentUs: FloatArray + get() { + + val doseUs = FloatArray((this.durationMinutes / 30).toInt()) + for (i in doseUs.indices) { + doseUs[i] = this.percent.toFloat() + } + + return doseUs + } + + val endTimestamp: Long + get() = if (this.startTimestamp == 0L) 0 else this.startTimestamp + TimeUnit.MINUTES.toMillis(this.durationMinutes) + + val doseUnitText: String + get() = String.format("%s U/hr", FloatFormatters.insulin(doseUnitPerHour)) + + val remainTimeText: String + get() { + var diff = endTimestamp - System.currentTimeMillis() + if (diff < 0) diff = 0 + val remainTime = CommonUtils.getRemainHourMin(diff) + return String.format("%02d:%02d", remainTime.first, remainTime.second) + } + + init { + initObject() + } + + fun initObject() { + this.unitDefinition = UnitOrPercent.U + this.doseUnitPerHour = 0f + this.percent = 0 + this.durationMinutes = 0 + this.startTimestamp = 0 + } + + fun getDoseUnitPerHourWithPercent(doseUnitPerHour: Float): Float { + return doseUnitPerHour * this.percent / 100f + } + + fun isGreaterThan(maxBasal: Float, normalBasalManager: NormalBasalManager): Boolean { + var maxTempBasal = 0f + if (this.unitDefinition == UnitOrPercent.U) { + maxTempBasal = this.doseUnitPerHour + } else if (this.unitDefinition == UnitOrPercent.P) { + val maxNormalBasal = normalBasalManager.normalBasal.getMaxBasal(durationMinutes) + maxTempBasal = FloatAdjusters.ROUND2_TEMP_BASAL_PROGRAM_RATE.apply(maxNormalBasal + getDoseUnitPerHourWithPercent(maxNormalBasal)) + } + return maxTempBasal > maxBasal + } + + override fun toString(): String { + return "TempBasal(startTimestamp=$startTimestamp, durationMinutes=$durationMinutes, doseUnitPerHour=$doseUnitPerHour, percent=$percent)" + } + + companion object { + fun createAbsolute( _durationMinutes: Long, _doseUnitPerHour: Float): TempBasal { + val b = TempBasal() + b.durationMinutes = _durationMinutes + b.doseUnitPerHour = _doseUnitPerHour + b.unitDefinition = UnitOrPercent.U + return b + } + fun createPercent( _durationMinutes: Long, _percent: Int): TempBasal { + val b = TempBasal() + b.durationMinutes = _durationMinutes + b.percent = _percent + b.unitDefinition = UnitOrPercent.P + return b + } + } + +} diff --git a/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/TempBasalManager.kt b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/TempBasalManager.kt new file mode 100644 index 0000000000..5cccec3270 --- /dev/null +++ b/pump/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/vo/TempBasalManager.kt @@ -0,0 +1,63 @@ +package info.nightscout.androidaps.plugins.pump.eopatch.vo + +import com.google.common.base.Preconditions +import info.nightscout.androidaps.plugins.pump.eopatch.CommonUtils +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.UnitOrPercent +import info.nightscout.shared.sharedPreferences.SP +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.subjects.BehaviorSubject + +class TempBasalManager : IPreference{ + @Transient + private val subject: BehaviorSubject = BehaviorSubject.create() + + var startedBasal: TempBasal? = null + + private var startTimestamp = 0L + + private var endTimestamp = 0L + + var unit = UnitOrPercent.P + + fun clear(){ + startedBasal = null + startTimestamp = 0L + endTimestamp = 0L + } + + fun updateBasalRunning(tempBasal: TempBasal) { + Preconditions.checkNotNull(tempBasal) + + this.startedBasal = CommonUtils.clone(tempBasal) + this.startedBasal?.running = true + this.startTimestamp = System.currentTimeMillis() + } + + fun updateBasalStopped() { + this.startedBasal?.running = false + this.startedBasal?.startTimestamp = 0 + } + + fun update(other: TempBasalManager){ + this.startedBasal = other.startedBasal + startTimestamp = other.startTimestamp + endTimestamp = other.endTimestamp + unit = other.unit + } + + override fun observe(): Observable { + return subject.hide() + } + + override fun flush(sp: SP){ + val jsonStr = GsonHelper.sharedGson().toJson(this) + sp.putString(SettingKeys.TEMP_BASAL, jsonStr) + subject.onNext(this) + } + + override fun toString(): String { + return "TempBasalManager(startedBasal=$startedBasal, startTimestamp=$startTimestamp, endTimestamp=$endTimestamp, unit=$unit)" + } +} diff --git a/pump/eopatch/src/main/res/layout/activity_eopatch.xml b/pump/eopatch/src/main/res/layout/activity_eopatch.xml new file mode 100644 index 0000000000..178ff6982d --- /dev/null +++ b/pump/eopatch/src/main/res/layout/activity_eopatch.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pump/eopatch/src/main/res/layout/dialog_alarm.xml b/pump/eopatch/src/main/res/layout/dialog_alarm.xml new file mode 100644 index 0000000000..38661005c1 --- /dev/null +++ b/pump/eopatch/src/main/res/layout/dialog_alarm.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + +