diff --git a/app/build.gradle b/app/build.gradle
index e94c3d61ef..8790d898fe 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -163,6 +163,10 @@ android {
}
useLibrary "org.apache.http.legacy"
+
+ dataBinding {
+ enabled = true
+ }
}
allprojects {
@@ -193,6 +197,7 @@ dependencies {
implementation project(':omnipod-dash')
implementation project(':diaconn')
implementation project(':openhumans')
+ implementation project(':eopatch')
implementation fileTree(include: ['*.jar'], dir: 'libs')
diff --git a/app/libs/eopatch_core.aar b/app/libs/eopatch_core.aar
new file mode 100644
index 0000000000..1a8aafe38f
Binary files /dev/null and b/app/libs/eopatch_core.aar differ
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 62435f5d19..ac0b07554d 100644
--- a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt
@@ -37,6 +37,7 @@ import info.nightscout.androidaps.plugins.general.wear.WearPlugin
import info.nightscout.androidaps.plugins.general.xdripStatusline.StatusLinePlugin
import info.nightscout.androidaps.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
@@ -98,6 +99,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
@@ -180,6 +182,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
addPreferencesFromResourceIfEnabled(medtronicPumpPlugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(diaconnG8Plugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResource(R.xml.pref_pump, rootKey, config.PUMPDRIVERS)
+ addPreferencesFromResourceIfEnabled(eopatchPumpPlugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(virtualPumpPlugin, rootKey)
addPreferencesFromResourceIfEnabled(insulinOrefFreePeakPlugin, rootKey)
addPreferencesFromResourceIfEnabled(nsClientPlugin, 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 c206de3837..f2156a9b11 100644
--- a/app/src/main/java/info/nightscout/androidaps/di/AppComponent.kt
+++ b/app/src/main/java/info/nightscout/androidaps/di/AppComponent.kt
@@ -18,6 +18,7 @@ import info.nightscout.androidaps.insight.di.InsightModule
import info.nightscout.androidaps.plugin.general.openhumans.dagger.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.dagger.OmnipodDashModule
import info.nightscout.androidaps.plugins.pump.omnipod.eros.dagger.OmnipodErosModule
@@ -62,6 +63,7 @@ import javax.inject.Singleton
WorkersModule::class,
DiaconnG8Module::class,
OpenHumansModule::class,
+ EopatchModule::class,
SharedModule::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 1942773a03..67d21de1f2 100644
--- a/app/src/main/java/info/nightscout/androidaps/di/PluginsModule.kt
+++ b/app/src/main/java/info/nightscout/androidaps/di/PluginsModule.kt
@@ -40,6 +40,7 @@ import info.nightscout.androidaps.plugins.insulin.InsulinOrefUltraRapidActingPlu
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.mdi.MDIPlugin
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin
@@ -182,6 +183,12 @@ abstract class PluginsModule {
@IntKey(155)
abstract fun bindDiaconnG8Plugin(plugin: DiaconnG8Plugin): PluginBase
+ @Binds
+ @PumpDriver
+ @IntoMap
+ @IntKey(156)
+ abstract fun bindEopatchPumpPlugin(plugin: EopatchPumpPlugin): PluginBase
+
@Binds
@NotNSClient
@IntoMap
diff --git a/build.gradle b/build.gradle
index fa14964fbc..c7e414e5b0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -34,6 +34,10 @@ buildscript {
androidx_junit = '1.1.2'
androidx_rules = '1.4.0-alpha04'
+
+ timber_version = "4.7.1"
+ rxandroidble_version = '1.12.1'
+ replayshare_version = '2.2.0'
}
repositories {
google()
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 a7ac2b5596..e56f421819 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 fc0d025e80..3e97afc8d0 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 1ceac452df..5f9d9c0571 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
@@ -24,6 +24,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 a36883adae..79df319936 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
@@ -311,7 +311,24 @@ enum class PumpType {
baseBasalStep = 0.01,
baseBasalSpecialSteps = null,
pumpCapability = PumpCapability.DanaWithHistoryCapabilities,
- source = Sources.DiaconnG8);
+ 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,
+ source = Sources.EOPatch2);
val description: String
var manufacturer: ManufacturerType? = null
@@ -393,6 +410,7 @@ enum class PumpType {
InterfaceIDs.PumpType.MDI -> MDI
InterfaceIDs.PumpType.USER -> USER
InterfaceIDs.PumpType.DIACONN_G8 -> DIACONN_G8
+ InterfaceIDs.PumpType.EOPATCH2 -> EOFLOW_EOPATCH2
}
}
@@ -511,5 +529,6 @@ enum class PumpType {
MDI -> InterfaceIDs.PumpType.MDI
USER -> InterfaceIDs.PumpType.USER
DIACONN_G8 -> InterfaceIDs.PumpType.DIACONN_G8
+ EOFLOW_EOPATCH2 -> InterfaceIDs.PumpType.EOPATCH2
}
}
\ No newline at end of file
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 fb667823e4..2e55678069 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
@@ -137,6 +137,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 0b1ac8a3a4..112a0d4f9f 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
@@ -92,6 +92,7 @@ class UserEntryPresentationHelper @Inject constructor(
Sources.Omnipod -> R.drawable.ic_pod_128
Sources.OmnipodEros -> R.drawable.ic_pod_128
Sources.OmnipodDash -> R.drawable.ic_pod_128
+ 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 d310b71a91..74a11cb377 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -41,6 +41,8 @@ files:
translation: /automation/src/main/res/values-%android_code%/strings.xml
- source: /diaconn/src/main/res/values/strings.xml
translation: /diaconn/src/main/res/values-%android_code%/strings.xml
+ - source: /eopatch/src/main/res/values/strings.xml
+ translation: /eopatch/src/main/res/values-%android_code%/strings.xml
- source: /openhumans/src/main/res/values/strings.xml
translation: /openhumans/src/main/res/values-%android_code%/strings.xml
translate_attributes: 0
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 0477dc1c8d..7c8ea6ea22 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;
companion object {
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 0b0db5bf82..f18fd16a79 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
@@ -168,6 +168,7 @@ data class UserEntry(
Omnipod, //No entry currently
OmnipodEros,
OmnipodDash, //No entry currently
+ EOPatch2,
MDI,
VirtualPump,
SMS, //From SMS plugin
diff --git a/eopatch/.gitignore b/eopatch/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/eopatch/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/eopatch/build.gradle b/eopatch/build.gradle
new file mode 100644
index 0000000000..e94c2bd4bd
--- /dev/null
+++ b/eopatch/build.gradle
@@ -0,0 +1,49 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-kapt'
+apply plugin: 'kotlin-allopen'
+apply plugin: 'com.hiya.jacoco-android'
+
+apply from: "${project.rootDir}/gradle/android_dependencies.gradle"
+apply from: "${project.rootDir}/gradle/android_module_dependencies.gradle"
+apply from: "${project.rootDir}/gradle/test_dependencies.gradle"
+apply from: "${project.rootDir}/gradle/jacoco_global.gradle"
+
+android {
+ dataBinding {
+ enabled = true
+ }
+}
+
+allprojects {
+ repositories {
+ flatDir {
+ dirs 'libs'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation (name: 'eopatch_core', ext: 'aar')
+ implementation project(':core')
+ implementation project(':shared')
+ implementation project(':database')
+
+ implementation 'androidx.core:core-ktx:1.6.0'
+ implementation 'androidx.appcompat:appcompat:1.3.1'
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+
+ // RxJava
+ implementation "io.reactivex.rxjava2:rxjava:$rxjava_version"
+ implementation "io.reactivex.rxjava2:rxkotlin:$rxkotlin_version"
+ implementation "io.reactivex.rxjava2:rxandroid:$rxandroid_version"
+
+ //RxAndroidBle
+ implementation "com.polidea.rxandroidble2:rxandroidble:$rxandroidble_version"
+ implementation "com.jakewharton.rx2:replaying-share:$replayshare_version"
+
+ // Log
+ implementation "com.jakewharton.timber:timber:$timber_version"
+}
\ No newline at end of file
diff --git a/eopatch/libs/eopatch_core.aar b/eopatch/libs/eopatch_core.aar
new file mode 100644
index 0000000000..1a8aafe38f
Binary files /dev/null and b/eopatch/libs/eopatch_core.aar differ
diff --git a/eopatch/proguard-rules.pro b/eopatch/proguard-rules.pro
new file mode 100644
index 0000000000..481bb43481
--- /dev/null
+++ b/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/eopatch/src/androidTest/java/info/nightscout/androidaps/plugins/pump/eopatch/ExampleInstrumentedTest.kt b/eopatch/src/androidTest/java/info/nightscout/androidaps/plugins/pump/eopatch/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000000..5a38981b80
--- /dev/null
+++ b/eopatch/src/androidTest/java/info/nightscout/androidaps/plugins/pump/eopatch/ExampleInstrumentedTest.kt
@@ -0,0 +1,25 @@
+package info.nightscout.androidaps.plugins.pump.eopatch
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.eoflow.patch", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/eopatch/src/main/AndroidManifest.xml b/eopatch/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..bd032c2f01
--- /dev/null
+++ b/eopatch/src/main/AndroidManifest.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/AppConstant.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/AppConstant.kt
new file mode 100644
index 0000000000..c12f177563
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/AppConstant.kt
@@ -0,0 +1,87 @@
+package info.nightscout.androidaps.plugins.pump.eopatch
+
+import java.util.concurrent.TimeUnit
+
+interface AppConstant {
+ companion object {
+
+ val BASAL_MIN_AMOUNT = 0.05f
+ val CLICK_THROTTLE = 600L
+
+ /**
+ * Bluetooth Connection State
+ */
+ val BT_STATE_NOT_CONNECT = 1
+ val BT_STATE_CONNECTED = 2
+
+
+ val INSULIN_DECIMAL_PLACE_VAR = 100f // 10.f; 소수점자리수 계산상수 (100.f 는 두자리)
+
+ // 패치 1P = 1 cycle = 0.1U
+ const val INSULIN_UNIT_P = 0.05f // 최소 주입 단위
+
+ val INSULIN_UNIT_MIN_U = 0f
+ val INSULIN_UNIT_STEP_U = INSULIN_UNIT_P
+
+ /**
+ * On/Off
+ */
+ val OFF = 0
+ val ON = 1
+
+ /**
+ * Pump Duration, Interval
+ */
+ val PUMP_DURATION_MILLI = TimeUnit.SECONDS.toMillis(4) // 15;
+ val PUMP_RESOLUTION = INSULIN_UNIT_P
+
+ /**
+ * Basal
+ */
+ val BASAL_RATE_PER_HOUR_MIN = BASAL_MIN_AMOUNT
+ val BASAL_RATE_PER_HOUR_STEP = INSULIN_UNIT_STEP_U
+ val BASAL_RATE_PER_HOUR_MAX = 15.0f // 30.0f; 30.00U/hr
+
+ val SEGMENT_MAX_SIZE_48 = 48
+ val SEGMENT_MAX_SIZE = SEGMENT_MAX_SIZE_48
+
+ val SEGMENT_COUNT_MAX = SEGMENT_MAX_SIZE_48
+
+ /**
+ * Bolus
+ */
+ val BOLUS_NORMAL_ID = 0x1
+ val BOLUS_EXTENDED_ID = 0x2
+
+ val BOLUS_ACTIVE_OFF = OFF
+ val BOLUS_ACTIVE_NORMAL = 0x01
+ val BOLUS_ACTIVE_EXTENDED_WAIT = 0x2
+ val BOLUS_ACTIVE_EXTENDED = 0x04
+ val BOLUS_ACTIVE_DISCONNECTED = 0x08
+
+ val BOLUS_UNIT_MIN = BASAL_MIN_AMOUNT
+ val BOLUS_UNIT_STEP = INSULIN_UNIT_STEP_U
+ val BOLUS_UNIT_MAX = 25.0f // 30.0f;
+ val BOLUS_DELIVER_MIN = 0.0f
+
+
+
+ /* Wizard */
+ val WIZARD_STEP_MAX = 24
+
+ val INFO_REMINDER_DEFAULT_VALUE = 1
+
+
+ val DAY_START_MINUTE = 0 * 60
+ val DAY_END_MINUTE = 24 * 60
+
+
+ val SNOOZE_INTERVAL_STEP = 5
+
+ /* Insulin Duration */
+ val INSULIN_DURATION_MIN = 2.0f
+ val INSULIN_DURATION_MAX = 8.0f
+ val INSULIN_DURATION_STEP = 0.5f
+
+ }
+}
\ No newline at end of file
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/CommonUtils.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/CommonUtils.kt
new file mode 100644
index 0000000000..f2d9c2fb5b
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/CommonUtils.kt
@@ -0,0 +1,178 @@
+package info.nightscout.androidaps.plugins.pump.eopatch
+
+
+import android.content.Context
+import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil
+import io.reactivex.disposables.Disposable
+import java.util.*
+import java.util.function.Function
+
+object CommonUtils {
+ val TO_INT = Function { it.toInt() }
+ val TO_FLOAT = Function { it.toFloat() }
+ val TO_STRING = Function { it.toString() }
+ val TO_CLOCK = Function{ num -> String.format(Locale.US, "%d:%02d", num.toInt() / 60, num.toInt() % 60) }
+
+ @JvmStatic fun dispose(vararg disposable: Disposable?) {
+ for (d in disposable){
+ d?.let {
+ if (!it.isDisposed()) {
+ it.dispose()
+ }
+ }
+ }
+ }
+
+ @JvmStatic fun nullSafe(ch: CharSequence?): String {
+ if (ch == null)
+ return ""
+ val str = ch.toString()
+ return str
+ }
+
+ @JvmStatic fun hasText(str: CharSequence?): Boolean {
+ if (str == null || str.length == 0) {
+ return false
+ }
+ val strLen = str.length
+ for (i in 0 until strLen) {
+ if (!Character.isWhitespace(str[i])) {
+ return true
+ }
+ }
+ return false
+ }
+
+ @JvmStatic fun hasText(str: String?): Boolean {
+ return str?.let{hasText(it as CharSequence)}?:false
+ }
+
+ @JvmStatic fun isStringEmpty(cs: CharSequence?): Boolean {
+ return cs == null || cs.length == 0
+ }
+
+ @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 getTimeString(millis: Long): String {
+ val c = Calendar.getInstance()
+ c.timeInMillis = millis
+ return getTimeString(c)
+ }
+
+ fun getTimeString(c: Calendar): String {
+ return String.format(Locale.US, "%02d:%02d",
+ c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE))
+ }
+
+ fun bytesToStringArray(byteArray: ByteArray?): String {
+ if (byteArray == null || byteArray.size == 0) {
+ return "null"
+ }
+
+ val sb = StringBuilder()
+ for (b in byteArray) {
+ if (sb.length > 0) {
+ sb.append(String.format(" %02x", b))
+ } else {
+ sb.append(String.format("0x%02x", b))
+ }
+ }
+ return sb.toString()
+ }
+
+ fun insulinFormat(): String {
+ if (AppConstant.INSULIN_UNIT_STEP_U == 0.1f) {
+ return "%.1f"
+ }
+ return if (AppConstant.INSULIN_UNIT_STEP_U == 0.05f) {
+ "%.2f"
+ } else "%.2f 인슐린출력형식추가하셈"
+
+ }
+
+ fun convertInsulinFormat(context: Context, strResId: Int): String {
+ if (AppConstant.INSULIN_UNIT_STEP_U == 0.1f) {
+ return context.getString(strResId).replace(".2f", ".1f")
+ }
+ if (AppConstant.INSULIN_UNIT_STEP_U == 0.05f) {
+ return context.getString(strResId)
+ }
+
+ return context.getString(strResId).replace(".2f", ".2f 인슐린출력형식추가하셈")
+
+ }
+
+ fun getRemainHourMin(timeMillis: Long): Pair {
+ val diffHours: Long
+ var diffMinutes: Long
+
+ if (timeMillis >= 0) {
+ diffMinutes = Math.abs(timeMillis / (60 * 1000) % 60) + 1
+ if (diffMinutes == 60L) {
+ diffMinutes = 0
+ diffHours = Math.abs(timeMillis / (60 * 60 * 1000)) + 1
+ } else {
+ diffHours = Math.abs(timeMillis / (60 * 60 * 1000))
+ }
+ } else {
+ diffMinutes = Math.abs(timeMillis / (60 * 1000) % 60)
+ diffHours = Math.abs(timeMillis / (60 * 60 * 1000))
+ }
+ return Pair(diffHours, diffMinutes)
+ }
+
+ fun getTimeString(minutes: Int): String {
+ return String.format("%d:%02d", minutes / 60, minutes % 60)
+ }
+
+ fun getTimeString_hhmm(minutes: Int): String {
+ return String.format("%02d:%02d", minutes / 60, minutes % 60)
+ }
+
+ @JvmStatic
+ fun generatePumpId(date: Long, typeCode: Long = 0): Long {
+ return DateTimeUtil.toATechDate(date) * 100L + typeCode
+ }
+
+ @JvmStatic
+ fun nearlyEqual(a: Float, b: Float, epsilon: Float): Boolean {
+ val absA = Math.abs(a)
+ val absB = Math.abs(b)
+ val diff = Math.abs(a - b)
+ return if (a == b) { // shortcut, handles infinities
+ true
+ } else if (a == 0f || b == 0f || absA + absB < java.lang.Float.MIN_NORMAL) {
+ // a or b is zero or both are extremely close to it
+ // relative error is less meaningful here
+ diff < epsilon * java.lang.Float.MIN_NORMAL
+ } else { // use relative error
+ diff / Math.min(absA + absB, Float.MAX_VALUE) < epsilon
+ }
+ }
+
+ @JvmStatic
+ fun nearlyNotEqual(a: Float, b: Float, epsilon: Float): Boolean {
+ return !nearlyEqual(a, b, epsilon)
+ }
+
+ @JvmStatic
+ fun clone(src: T): T {
+ return GsonHelper.sharedGson().fromJson(GsonHelper.sharedGson().toJson(src), src.javaClass)
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EONotification.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EONotification.kt
new file mode 100644
index 0000000000..f2ab48588e
--- /dev/null
+++ b/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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EoPatchRxBus.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EoPatchRxBus.kt
new file mode 100644
index 0000000000..be24015956
--- /dev/null
+++ b/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.Observable
+import io.reactivex.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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EopatchPumpPlugin.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EopatchPumpPlugin.kt
new file mode 100644
index 0000000000..a829124005
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/EopatchPumpPlugin.kt
@@ -0,0 +1,588 @@
+package info.nightscout.androidaps.plugins.pump.eopatch
+
+import android.content.Context
+import android.os.SystemClock
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+import dagger.android.HasAndroidInjector
+import info.nightscout.androidaps.data.DetailedBolusInfo
+import info.nightscout.androidaps.interfaces.Profile
+import info.nightscout.androidaps.data.PumpEnactResult
+import info.nightscout.androidaps.events.EventAppInitialized
+import info.nightscout.androidaps.events.EventPreferenceChange
+import info.nightscout.androidaps.interfaces.*
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+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.EventOverviewBolusProgress
+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.extension.takeOne
+import info.nightscout.androidaps.plugins.pump.eopatch.extension.with
+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.resources.ResourceHelper
+import info.nightscout.shared.sharedPreferences.SP
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.functions.Consumer
+import io.reactivex.schedulers.Schedulers
+import io.reactivex.subjects.BehaviorSubject
+import org.json.JSONObject
+import timber.log.Timber
+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 context: Context,
+ private val rxBus: RxBus,
+ private val sp: SP,
+ private val profileFunction: ProfileFunction,
+ private val activePlugin: ActivePlugin,
+ 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.getName())
+ .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()
+
+ var mPumpType: PumpType = PumpType.EOFLOW_EOPATCH2
+ private set
+ private var mLastDataTime: Long = 0
+ private val mPumpDescription = PumpDescription(mPumpType)
+
+ init {
+ if (BuildConfig.DEBUG) {
+ Timber.plant(Timber.DebugTree())
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ mDisposables.add(rxBus
+ .toObservable(EventPreferenceChange::class.java)
+ .observeOn(Schedulers.io())
+ .subscribe({ event: EventPreferenceChange ->
+ if (event.isChanged(rh, SettingKeys.LOW_RESERVIOR_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(Schedulers.io())
+ .subscribe({ event: EventAppInitialized? ->
+ aapsLogger.debug(LTag.PUMP,"EventAppInitialized")
+ preferenceManager.init()
+ patchmanager.init()
+ alarmManager.init()
+ }) { throwable: Throwable -> fabricPrivacy.logException(throwable) }
+ )
+ }
+
+ override fun specialEnableCondition(): Boolean {
+ //BG -> FG 시 패치 활성화 재진행 및 미처리 알람 발생
+ if(preferenceManager.isInitDone()) {
+ patchmanager.checkActivationProcess()
+ alarmManager.restartAll()
+ }
+ return super.specialEnableCondition()
+ }
+
+ override fun specialShowInListCondition(): Boolean {
+ return super.specialShowInListCondition()
+ }
+
+ override fun onStop() {
+ super.onStop()
+ aapsLogger.debug(LTag.PUMP, "EOPatchPumpPlugin onStop()");
+ }
+
+ override fun onStateChange(type: PluginType?, oldState: State?, newState: State?) {
+ super.onStateChange(type, oldState, newState)
+ }
+
+ override fun preprocessPreferences(preferenceFragment: PreferenceFragmentCompat) {
+ super.preprocessPreferences(preferenceFragment)
+ }
+
+ override fun updatePreferenceSummary(pref: Preference) {
+ super.updatePreferenceSummary(pref)
+ }
+
+ override fun isUnreachableAlertTimeoutExceeded(alertTimeoutMilliseconds: Long): Boolean {
+ return super.isUnreachableAlertTimeoutExceeded(alertTimeoutMilliseconds)
+ }
+
+ override fun setNeutralTempAtFullHour(): Boolean {
+ return super.setNeutralTempAtFullHour()
+ }
+
+ override fun isInitialized(): Boolean {
+ val isInit = isConnected() && patchmanager.isActivated()
+ return isInit
+ }
+
+ override fun isSuspended(): Boolean {
+ return patchmanager.patchState.isNormalBasalPaused
+ }
+
+ override fun isBusy(): Boolean {
+ return false
+ }
+
+ override fun isConnected(): Boolean {
+ return 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")
+ }
+ 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)
+ patchmanager.startBasal(nb)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({ response ->
+ if (response.isSuccess) {
+ preferenceManager.getNormalBasalManager().normalBasal = nb
+ preferenceManager.flushNormalBasalManager()
+ }
+ result.onNext(response.isSuccess)
+ }, { throwable ->
+ result.onNext(false)
+ })
+
+ do{
+ SystemClock.sleep(100)
+ }while(isSuccess == null)
+
+ disposable.dispose()
+ aapsLogger.info(LTag.PUMP, "Basal Profile was set: ${isSuccess?:false}");
+ return PumpEnactResult(injector).apply{ success = isSuccess?:false }
+ }
+ }else{
+ preferenceManager.getNormalBasalManager().setNormalBasal(profile)
+ preferenceManager.flushNormalBasalManager()
+ return PumpEnactResult(injector)
+ }
+ }
+
+ 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
+ }
+ val reserviorLevel = patchmanager.patchState.remainedInsulin.toDouble()
+
+ return (reserviorLevel > 50.0).takeOne(50.0, reserviorLevel)
+ }
+
+ override val batteryLevel: Int
+ get() {
+ if(patchmanager.isActivated) {
+ return patchmanager.patchState.batteryLevel()
+ }else{
+ return 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
+ }
+
+ patchmanager.startCalculatorBolus(detailedBolusInfo)
+ .doOnSuccess {
+ mLastDataTime = System.currentTimeMillis()
+ }.subscribe({
+ result.onNext(it.isSuccess)
+ }, {
+ result.onNext(false)
+ })
+
+ do{
+ SystemClock.sleep(100)
+ if(patchmanager.patchConnectionState.isConnected) {
+ var delivering = patchmanager.bolusCurrent.nowBolus.injected
+ rxBus.send(EventOverviewBolusProgress.apply {
+ status = rh.gs(R.string.bolusdelivering, delivering)
+ percent = min((delivering / detailedBolusInfo.insulin * 100).toInt(), 100)
+ })
+ }
+ }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() {
+ patchmanager.stopNowBolus()
+ .with()
+ .subscribe { it ->
+ rxBus.send(EventOverviewBolusProgress.apply {
+ status = rh.gs(R.string.bolusdelivered, (it.injectedBolusAmount * 0.05f)) //todo stoped 메세지로 변경 필요
+ })
+ }
+ }
+
+ 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)
+ .with()
+ .doOnSuccess {
+ preferenceManager.getTempBasalManager().startedBasal = tb
+ preferenceManager.getTempBasalManager().startedBasal?.startTimestamp = System.currentTimeMillis()
+ 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 { it -> 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)
+ .with()
+ .doOnSuccess {
+ preferenceManager.getTempBasalManager().startedBasal = tb
+ preferenceManager.getTempBasalManager().startedBasal?.startTimestamp = System.currentTimeMillis()
+ 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 { it -> 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 { it -> 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 { it -> 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 { it -> 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 += "Batt: ${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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/FloatFormatters.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/FloatFormatters.kt
new file mode 100644
index 0000000000..badd141124
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/FloatFormatters.kt
@@ -0,0 +1,46 @@
+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, CommonUtils.insulinFormat(), 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).toString() +" "+ suffix!!
+ }
+ }
+
+ fun fat(value: Float): String {
+ return FAT.apply(value)
+ }
+
+ fun fat(value: Float, suffix: String?): String {
+ return if (CommonUtils.isStringEmpty(suffix)) {
+ FAT.apply(value)
+ } else {
+ FAT.apply(value).toString() + 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).toString() +" " + suffix!!
+ }
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/GsonHelper.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/GsonHelper.kt
new file mode 100644
index 0000000000..9586dd5b85
--- /dev/null
+++ b/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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/OsAlarmReceiver.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/OsAlarmReceiver.kt
new file mode 100644
index 0000000000..77ca7204ea
--- /dev/null
+++ b/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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/OsAlarmService.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/OsAlarmService.java
new file mode 100644
index 0000000000..141031e8cf
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/OsAlarmService.java
@@ -0,0 +1,81 @@
+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.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) {
+//// CommonUtils.dispose(mNotificationDisposable);
+// Notification builder = getNotification(this);
+// startForeground(FOREGROUND_NOTIFICATION_ID, builder);
+// startExerciseOrSleepMode(this);
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/RxAction.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/RxAction.kt
new file mode 100644
index 0000000000..052f23df8b
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/RxAction.kt
@@ -0,0 +1,113 @@
+package info.nightscout.androidaps.plugins.pump.eopatch
+
+import io.reactivex.*
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
+import org.reactivestreams.Subscription
+import timber.log.Timber
+import java.util.concurrent.TimeUnit
+
+enum class RxVoid {
+ INSTANCE
+}
+
+class SilentObserver : MaybeObserver, SingleObserver, Observer, FlowableSubscriber {
+ override fun onSubscribe(d: Disposable) {}
+ override fun onSuccess(t: T) {}
+ override fun onError(e: Throwable) = Timber.d(e, "SilentObserver.onError() ignore")
+ override fun onComplete() {}
+ override fun onNext(t: T) {}
+ override fun onSubscribe(s: Subscription) {}
+}
+
+object RxAction {
+ private fun msleep(millis: Long) {
+ if (millis <= 0)
+ return
+ try {
+ Thread.sleep(millis)
+ } catch (e: InterruptedException) {
+ }
+
+ }
+
+ 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 { o ->
+ Single.fromCallable {
+ action.run()
+ RxVoid.INSTANCE
+ }
+ }
+ }
+
+ fun safeSingle(action: Runnable, delayMs: Long, scheduler: Scheduler): Single<*> {
+ return single(action, delayMs, scheduler)
+ }
+
+ @JvmOverloads
+ fun runOnComputationThread(action: Runnable, delayMs: Long = 0) {
+ single(action, delayMs, Schedulers.computation()).subscribe(SilentObserver())
+ }
+
+ @JvmOverloads
+ fun runOnIoThread(action: Runnable, delayMs: Long = 0) {
+ single(action, delayMs, Schedulers.io()).subscribe(SilentObserver())
+ }
+
+ @JvmOverloads
+ fun runOnNewThread(action: Runnable, delayMs: Long = 0) {
+ single(action, delayMs, Schedulers.newThread()).subscribe(SilentObserver())
+ }
+
+ @JvmOverloads
+ fun runOnMainThread(action: Runnable, delayMs: Long = 0) {
+ single(action, delayMs, AndroidSchedulers.mainThread()).subscribe(SilentObserver())
+ }
+
+ @JvmOverloads
+ fun safeRunOnComputationThread(action: Runnable, delayMs: Long = 0) {
+ safeSingle(action, delayMs, Schedulers.computation()).subscribe(SilentObserver())
+ }
+
+ @JvmOverloads
+ fun safeRunOnIoThread(action: Runnable, delayMs: Long = 0) {
+ safeSingle(action, delayMs, Schedulers.io()).subscribe(SilentObserver())
+ }
+
+ @JvmOverloads
+ fun safeRunOnNewThread(action: Runnable, delayMs: Long = 0) {
+ safeSingle(action, delayMs, Schedulers.newThread()).subscribe(SilentObserver())
+ }
+
+ @JvmOverloads
+ fun safeRunOnMainThread(action: Runnable, delayMs: Long = 0) {
+ safeSingle(action, delayMs, AndroidSchedulers.mainThread()).subscribe(SilentObserver())
+ }
+
+
+ fun singleOnMainThread(action: Runnable, delayMs: Long): Single<*> {
+ return single(action, delayMs, AndroidSchedulers.mainThread())
+ }
+
+ fun singleOnComputationThread(action: Runnable, delayMs: Long): Single<*> {
+ return single(action, delayMs, Schedulers.computation())
+ }
+
+ fun singleOnIoThread(action: Runnable, delayMs: Long): Single<*> {
+ return single(action, delayMs, Schedulers.io())
+ }
+
+ fun singleOnNewThread(action: Runnable, delayMs: Long): Single<*> {
+ return single(action, delayMs, Schedulers.newThread())
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmCode.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmCode.kt
new file mode 100644
index 0000000000..cb410327bd
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmCode.kt
@@ -0,0 +1,129 @@
+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(defaultName: String, messageResId: Int) {
+ A002("Empty reservoir", R.string.string_a002),
+ A003("Patch expired", R.string.string_a003),
+ A004("Occlusion", R.string.string_a004),
+ A005("Power on self test failure", R.string.string_a005),
+ A007("Inappropriate temperature", R.string.string_a007),
+ A016("Needle insertion Error", R.string.string_a016),
+ A018("Patch battery Error", R.string.string_a018),
+ A019("Patch battery Error", R.string.string_a019),
+ A020("Patch activation Error", R.string.string_a020),
+ A022("Patch Error", R.string.string_a022),
+ A023("Patch Error", R.string.string_a023),
+ A034("Patch Error", R.string.string_a034),
+ A041("Patch Error", R.string.string_a041),
+ A042("Patch Error", R.string.string_a042),
+ A043("Patch Error", R.string.string_a043),
+ A044("Patch Error", R.string.string_a044),
+ A106("Patch Error", R.string.string_a106),
+ A107("Patch Error", R.string.string_a107),
+ A108("Patch Error", R.string.string_a108),
+ A116("Patch Error", R.string.string_a116),
+ A117("Patch Error", R.string.string_a117),
+ A118("Patch Error", R.string.string_a118),
+ B001("End of insulin suspend", R.string.string_b001),
+ B003("Low reservoir", R.string.string_b003),
+ B005("Patch operating life expired", R.string.string_b005),
+ B006("Patch will expire soon", R.string.string_b006),
+ B012("Incomplete Patch activation", R.string.string_b012),
+ B018("Patch battery low", R.string.string_b018);
+
+ val type: Char
+ val code: Int
+ val resId: Int
+ 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 osAlarmId: Int
+ get() = (when (type) {
+ TYPE_ALARM -> 10000
+ TYPE_ALERT -> 20000
+ else -> 0
+ } + code ) * 1000 + 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
+
+ init {
+ type = name[0]
+ this.code = name.substring(1).toInt()
+ resId = messageResId
+ }
+
+ 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("com.eoflow.eomapp")
+ .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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmManager.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmManager.kt
new file mode 100644
index 0000000000..3929625d1c
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmManager.kt
@@ -0,0 +1,171 @@
+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.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+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.eopatch.alarm.AlarmCode.*
+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.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.FabricPrivacy
+import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.shared.sharedPreferences.SP
+import io.reactivex.Observable
+import io.reactivex.Single
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.schedulers.Schedulers
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import javax.inject.Singleton
+
+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 pm: IPreferenceManager
+ @Inject lateinit var mAlarmRegistry: IAlarmRegistry
+
+ private lateinit var mAlarmProcess: AlarmProcess
+
+ private var compositeDisposable: CompositeDisposable = CompositeDisposable()
+
+ @Inject
+ fun onInit() {
+ mAlarmProcess = AlarmProcess(patchManager, rxBus)
+ }
+
+ override fun init(){
+ EoPatchRxBus.listen(EventEoPatchAlarm::class.java)
+ .map { it -> it.alarmCodes }
+ .doOnNext { aapsLogger.info(LTag.PUMP,"EventEoPatchAlarm Received") }
+ .concatMap {
+ Observable.fromArray(it)
+ .observeOn(Schedulers.io())
+ .subscribeOn(AndroidSchedulers.mainThread())
+ .doOnNext { alarmCodes ->
+ alarmCodes.forEach {
+ aapsLogger.info(LTag.PUMP,"alarmCode: ${it.name}")
+ val valid = isValid(it)
+ if (valid) {
+ if (it.alarmCategory == AlarmCategory.ALARM || it == B012) {
+ showAlarmDialog(it)
+ } else {
+ showNotification(it)
+ }
+
+ updateState(it, AlarmState.FIRED)
+ }else{
+ updateState(it, AlarmState.HANDLE)
+ }
+ }
+ }
+
+ }
+ .subscribe({}, { throwable: Throwable -> fabricPrivacy.logException(throwable) })
+ }
+
+ override fun restartAll() {
+ val now = System.currentTimeMillis()
+ val occuredAlarm = pm.getAlarms().occured.clone() as HashMap
+ val registeredAlarm = pm.getAlarms().registered.clone() as HashMap
+ compositeDisposable.clear()
+ if(occuredAlarm.isNotEmpty()){
+ EoPatchRxBus.publish(EventEoPatchAlarm(occuredAlarm.keys))
+ }
+
+ if(registeredAlarm.isNotEmpty()){
+ registeredAlarm.forEach { raEntry ->
+ compositeDisposable.add(
+ mAlarmRegistry.add(raEntry.key, Math.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 occurredTimestamp: Long = pm.getPatchConfig().patchWakeupTimestamp + TimeUnit.SECONDS.toMillis(timeOffset)
+ val notification = EONotification(Notification.EOELOW_PATCH_ALERTS + (alarmCode.aeCode + 10000), resourceHelper.gs(alarmCode.resId), Notification.URGENT)
+
+ notification.action(R.string.confirm) {
+ 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){
+ updateState(alarmCode, AlarmState.HANDLE)
+ }else{
+ rxBus.send(EventNewNotification(notification))
+ }
+ }
+ }
+ notification.soundId = R.raw.error
+ notification.date = occurredTimestamp
+ rxBus.send(EventNewNotification(notification))
+ }
+
+ private fun updateState(alarmCode: AlarmCode, state: AlarmState){
+ when(state){
+ AlarmState.REGISTER -> pm.getAlarms().register(alarmCode, 0)
+ AlarmState.FIRED -> pm.getAlarms().occured(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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmProcess.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmProcess.kt
new file mode 100644
index 0000000000..7f440d6527
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmProcess.kt
@@ -0,0 +1,135 @@
+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.createIntentForChangePatch
+import info.nightscout.androidaps.plugins.pump.eopatch.ui.EopatchActivity.Companion.createIntentForCanularInsertionError
+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.code.DeactivationStatus
+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.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, true)
+ A007 -> inappropriateTemperatureAction(context)
+ A016 -> needleInsertionErrorAction(context)
+ B003, B018 -> Single.just(IAlarmProcess.ALARM_HANDLED)
+ B005, B006 -> Single.just(IAlarmProcess.ALARM_HANDLED)
+ B012 -> Single.just(IAlarmProcess.ALARM_HANDLED)
+ else -> 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) {
+ var 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 { dialog, which ->
+ 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, true, 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, goHome: Boolean): 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, goHome))
+ }
+ .flatMap { ok: DeactivationStatus? -> Single.just(IAlarmProcess.ALARM_HANDLED) }
+ }
+ }
+
+ private fun changPatchAction(context: Context): Single {
+ return Single.fromCallable {
+ startActivityWithSingleTop(context, createIntentForChangePatch(context))
+ IAlarmProcess.ALARM_HANDLED
+ }
+ }
+
+ private fun needleInsertionErrorAction(context: Context): Single {
+ return Single.fromCallable {
+ startActivityWithSingleTop(context, createIntentForCanularInsertionError(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)
+ .toSingle()
+ }
+ }
+}
\ No newline at end of file
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmRegistry.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmRegistry.kt
new file mode 100644
index 0000000000..c5e985087d
--- /dev/null
+++ b/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 info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.Companion.getUri
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.pump.eopatch.event.EventEoPatchAlarm
+import android.app.PendingIntent
+import android.app.AlarmManager.AlarmClockInfo
+import android.content.Context
+import android.content.Intent
+import info.nightscout.shared.logging.AAPSLogger
+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.code.PatchLifecycle
+import info.nightscout.androidaps.plugins.pump.eopatch.core.code.PatchAeCode
+import info.nightscout.androidaps.plugins.pump.eopatch.extension.observeOnMainThread
+import io.reactivex.Maybe
+import io.reactivex.Observable
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.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(alarmKey: 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
+
+ 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()
+ .observeOnMainThread()
+ .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().occured.let{
+ if(it.isNotEmpty()){
+ it.keys.forEach {
+ sources.add(
+ Maybe.just(it)
+ .observeOnMainThread()
+ .doOnSuccess { rxBus.send(EventDismissNotification(Notification.EOELOW_PATCH_ALERTS + (it.aeCode + 10000))) }
+ )
+ }
+ }
+ }
+ pm.getAlarms().registered.let{
+ if(it.isNotEmpty()){
+ it.keys.forEach {
+ sources.add(remove(it))
+ }
+ }
+ }
+ Maybe.concat(sources)
+ .subscribe {
+ pm.getAlarms().clear()
+ pm.flushAlarms()
+ }
+ }
+
+ else -> Unit
+ }
+ }
+ }
+
+ override fun add(alarmCode: AlarmCode, triggerAfter: Long, isFirst: Boolean): Maybe {
+ if(pm.getAlarms().occured.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.Companion.findByPatchAeCode(patchAeCodeItem.getAeValue()) != null}
+ .observeOn(AndroidSchedulers.mainThread())
+ .filter { patchAeCodes -> AlarmCode.findByPatchAeCode(patchAeCodes.getAeValue()) != null }
+ .flatMapMaybe{aeCodeResponse -> add(AlarmCode.findByPatchAeCode(aeCodeResponse.getAeValue())!!,0L, true)}
+ .subscribe()
+ )
+ }
+
+ private fun registerOsAlarm(alarmCode: AlarmCode, triggerTime: Long): Maybe {
+ return Maybe.fromCallable {
+ cancelOsAlarmInternal(alarmCode)
+ val pendingIntent = createPendingIntent(alarmCode, 0)
+ val now = System.currentTimeMillis()
+ mOsAlarmManager.setAlarmClock(AlarmClockInfo(triggerTime, pendingIntent), pendingIntent)
+ alarmCode
+ }
+ }
+
+ override fun remove(alarmCode: AlarmCode): Maybe {
+ if(pm.getAlarms().registered.containsKey(alarmCode)) {
+ return cancelOsAlarms(alarmCode)
+ .doOnSuccess {
+ pm.getAlarms().unregister(alarmCode)
+ pm.flushAlarms()
+ }
+ .map { integer: Int? -> alarmCode }
+ }else{
+ return 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, PendingIntent.FLAG_UPDATE_CURRENT)
+ }
+}
\ No newline at end of file
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmState.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/alarm/AlarmState.java
new file mode 100644
index 0000000000..4029b27300
--- /dev/null
+++ b/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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/bindingadapters/OnSafeClickListener.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/bindingadapters/OnSafeClickListener.kt
new file mode 100644
index 0000000000..236f5a5d65
--- /dev/null
+++ b/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 val MIN_CLICK_INTERVAL: Long = 1000
+ }
+}
\ No newline at end of file
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/bindingadapters/ViewBindingAdapter.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/bindingadapters/ViewBindingAdapter.kt
new file mode 100644
index 0000000000..bae87baf98
--- /dev/null
+++ b/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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/IPatchManager.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/IPatchManager.java
new file mode 100644
index 0000000000..7a6ead66b7
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/IPatchManager.java
@@ -0,0 +1,128 @@
+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.Observable;
+import io.reactivex.Single;
+
+public interface IPatchManager {
+
+ /*
+ @Deprecated
+ static IPatchManager shared() {
+ return BaseApplication.instance.getDataComponent().getPatchManager();
+ }
+ */
+ void init();
+
+ IPreferenceManager getPreferenceManager();
+
+ PatchConfig getPatchConfig();
+
+ boolean isActivated();
+
+ Single extends BaseResponse> resumeBasal();
+
+ Observable observePatchLifeCycle();
+
+ Observable observePatchState();
+
+ BleConnectionState getPatchConnectionState();
+
+ void connect();
+
+ void disconnect();
+
+ PatchState getPatchState();
+
+ void updatePatchState(PatchState state);
+
+ BolusCurrent getBolusCurrent();
+
+ Single deactivate(long timeout, boolean force);
+
+ Observable observePatchConnectionState();
+
+ Observable observeBolusCurrent();
+
+ void setConnection();
+
+ Single stopNowBolus();
+
+ Single stopExtBolus();
+
+ Single stopComboBolus();
+
+ Single extends BolusResponse> startQuickBolus(float nowDoseU, float exDoseU,
+ BolusExDuration exDuration);
+
+ Single extends BolusResponse> startCalculatorBolus(DetailedBolusInfo detailedBolusInfo);
+
+
+ Single infoReminderSet(boolean infoReminder);
+
+ Single setLowReservoir(int doseUnit, int hours);
+
+ Single updateConnection();
+
+ long getPatchExpiredTime();
+
+ Single startBasal(NormalBasal basal);
+
+ void updatePatchLifeCycle(PatchLifecycleEvent event);
+
+ Single startBond(String mac);
+
+ Single getPatchInfo(long timeout);
+
+ Single selfTest(long timeout);
+
+ Observable startPriming(long timeout, long count);
+
+ Single checkNeedleSensing(long timeout);
+
+ Single patchActivation(long timeout);
+
+ Single stopAeBeep(int aeCode);
+
+ Single startTempBasal(TempBasal tempBasal);
+
+ Single extends BaseResponse> pauseBasal(float pauseDurationHour);
+
+ Single scan(long timeout);
+
+ Single stopTempBasal();
+
+ Single getTemperature();
+
+ void initBasalSchedule();
+
+ void addBolusToHistory(DetailedBolusInfo originalDetailedBolusInfo);
+
+ void changeBuzzerSetting();
+
+ void changeReminderSetting();
+
+ void checkActivationProcess();
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManager.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManager.java
new file mode 100644
index 0000000000..d153642a93
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManager.java
@@ -0,0 +1,451 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble;
+
+import android.content.Context;
+import android.content.Intent;
+
+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.plugins.pump.common.defs.PumpType;
+import info.nightscout.androidaps.utils.DateUtil;
+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.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.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.code.BolusExDuration;
+import info.nightscout.androidaps.plugins.pump.eopatch.code.DeactivationStatus;
+import info.nightscout.androidaps.plugins.pump.eopatch.code.PatchLifecycle;
+import com.polidea.rxandroidble2.RxBleClient;
+import com.polidea.rxandroidble2.exceptions.BleException;
+import com.polidea.rxandroidble2.internal.RxBleLog;
+
+import java.util.concurrent.TimeUnit;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BasalScheduleSetResponse;
+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.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.code.SettingKeys;
+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.queue.commands.Command;
+import info.nightscout.androidaps.utils.resources.ResourceHelper;
+import info.nightscout.shared.sharedPreferences.SP;
+import io.reactivex.Observable;
+import io.reactivex.Single;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.CompositeDisposable;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.exceptions.OnErrorNotImplementedException;
+import io.reactivex.exceptions.UndeliverableException;
+import io.reactivex.plugins.RxJavaPlugins;
+import io.reactivex.schedulers.Schedulers;
+
+@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;
+
+ private IPatchScanner patchScanner;
+ private CompositeDisposable mCompositeDisposable = new CompositeDisposable();
+ private Disposable mConnectingDisposable = null;
+
+ @Inject
+ public PatchManager() {
+ setupRxAndroidBle();
+ }
+
+ private void setupRxAndroidBle() {
+ RxJavaPlugins.setErrorHandler(throwable -> {
+ if (throwable instanceof UndeliverableException) {
+ if (throwable.getCause() instanceof BleException) {
+ return;
+ }
+ aapsLogger.error(LTag.PUMPBTCOMM, "rx UndeliverableException Error Handler");
+ return;
+ } else if (throwable instanceof OnErrorNotImplementedException) {
+ aapsLogger.error(LTag.PUMPBTCOMM, "rx exception Error Handler");
+ return;
+ }
+ throw new RuntimeException("Unexpected Throwable in RxJavaPlugins error handler", throwable);
+ });
+ }
+
+ @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(AndroidSchedulers.mainThread())
+ .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(Schedulers.io())
+ .subscribeOn(AndroidSchedulers.mainThread())
+ .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() {
+ initBasalSchedule();
+ 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 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);
+ }
+
+ public Single startBasal(NormalBasal basal) {
+ return patchManager.startBasal(basal);
+ }
+
+ public Single extends BaseResponse> resumeBasal() {
+ return patchManager.resumeBasal();
+ }
+
+
+ public Single extends BaseResponse> pauseBasal(float pauseDurationHour) {
+ return patchManager.pauseBasal(pauseDurationHour);
+ }
+
+ //==============================================================================================
+ // IPatchManager interface [TEMP BASAL]
+ //==============================================================================================
+
+ public Single startTempBasal(TempBasal tempBasal) {
+ return patchManager.startTempBasal(tempBasal);
+ }
+
+ // 템프베이젤 주입 정지
+ // 템프베이젤이 정지되면 자동으로 노멀베이젤이 활성화된다
+ // 외부에서 호출된다. 즉 명시적으로 tempBasal 정지. 이 때는 normalBasal resume 은 PatchState 보고 처리.
+
+ public Single stopTempBasal() {
+ return patchManager.stopTempBasal();
+ }
+
+
+ public Single extends BolusResponse> startQuickBolus(float nowDoseU,
+ float exDoseU, BolusExDuration exDuration) {
+ return patchManager.startQuickBolus(nowDoseU, exDoseU, exDuration);
+ }
+
+
+ public Single extends BolusResponse> startCalculatorBolus(DetailedBolusInfo detailedBolusInfo) {
+ return patchManager.startCalculatorBolus(detailedBolusInfo);
+ }
+
+
+ public Single stopNowBolus() {
+ return patchManager.stopNowBolus();
+ }
+
+
+ public Single stopExtBolus() {
+ return patchManager.stopExtBolus();
+ }
+
+
+ public Single stopComboBolus(){
+ return patchManager.stopComboBolus();
+ }
+
+ public Single deactivate(long timeout, boolean force) {
+ return patchManager.deactivate(timeout, force);
+ }
+
+ public Single stopBuzz() {
+ return patchManager.stopBuzz();
+ }
+
+ 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 initBasalSchedule() {
+ if(pm.getNormalBasalManager().getNormalBasal() == null){
+ pm.getNormalBasalManager().setNormalBasal(profileFunction.getProfile());
+ pm.flushNormalBasalManager();
+ }
+ }
+
+ @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()) {
+ infoReminderSet(buzzer)
+ .observeOn(AndroidSchedulers.mainThread())
+ .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_RESERVIOR_REMINDERS(), 0);
+ int hours = sp.getInt(SettingKeys.Companion.getEXPIRATION_REMINDERS(), 0);
+ PatchConfig pc = pm.getPatchConfig();
+ if(pc.getLowReservoirAlertAmount() != doseUnit || pc.getPatchExpireAlertTime() != hours) {
+ if (isActivated()) {
+ setLowReservoir(doseUnit, hours)
+ .observeOn(AndroidSchedulers.mainThread())
+ .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().isOccuring(AlarmCode.A005)
+ && !pm.getAlarms().isOccuring(AlarmCode.A020)) {
+ RxAction.INSTANCE.runOnMainThread(() -> {
+ rxBus.send(new EventPatchActivationNotComplete());
+ });
+ }
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManagerImpl.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManagerImpl.java
new file mode 100644
index 0000000000..f77da843b8
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchManagerImpl.java
@@ -0,0 +1,793 @@
+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 info.nightscout.androidaps.data.DetailedBolusInfo;
+import info.nightscout.shared.logging.AAPSLogger;
+import info.nightscout.shared.logging.LTag;
+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.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.scan.PatchSelfTestResult;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BasalStop;
+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.*;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.code.BolusExDuration;
+import info.nightscout.androidaps.plugins.pump.eopatch.code.DeactivationStatus;
+
+import java.math.BigInteger;
+import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
+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.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.crypto.KeyAgreement;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.core.util.HexString;
+import info.nightscout.androidaps.plugins.pump.eopatch.code.SettingKeys;
+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.shared.sharedPreferences.SP;
+import io.reactivex.Observable;
+import io.reactivex.Scheduler;
+import io.reactivex.Single;
+import io.reactivex.disposables.CompositeDisposable;
+import io.reactivex.functions.Consumer;
+import io.reactivex.schedulers.Schedulers;
+
+@Singleton
+public class PatchManagerImpl/* implements IPatchConstant*/ {
+ @Inject IPreferenceManager pm;
+ @Inject Context context;
+ @Inject SP sp;
+ @Inject AAPSLogger aapsLogger;
+
+ @Inject StartBondTask START_BOND;
+ @Inject GetPatchInfoTask GET_PATCH_INFO;
+ @Inject SelfTestTask SELF_TEST;
+ @Inject PrimingTask START_PRIMING;
+ @Inject NeedleSensingTask START_NEEDLE_CHECK;
+
+ IBleDevice patch;
+ HexString hexString;
+
+ private CompositeDisposable compositeDisposable;
+
+ private Observable dateTimeChanged;
+
+ private static final long DEFAULT_API_TIME_OUT = 10; // SECONDS
+
+ private BuzzerStop BUZZER_STOP;
+ private GetTemperature TEMPERATURE_GET;
+ private BasalStop BASAL_STOP;
+ private StopAeBeep ALARM_ALERT_ERROR_BEEP_STOP;
+ private PublicKeySend PUBLIC_KEY_SET;
+ private SequenceGet SEQUENCE_GET;
+
+ @Inject
+ public PatchManagerImpl() {
+ compositeDisposable = new CompositeDisposable();
+ hexString = new HexString();
+
+ BUZZER_STOP = new BuzzerStop();
+ TEMPERATURE_GET = new GetTemperature();
+ BASAL_STOP = new BasalStop();
+ 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);
+
+ dateTimeChanged = RxBroadcastReceiver.Companion.create(context, filter);
+
+ compositeDisposable.add(
+ Observable.combineLatest(patch.observeConnected(), pm.observePatchLifeCycle(),
+ (connected, lifeCycle) -> (connected && lifeCycle.isActivated()))
+ .subscribeOn(Schedulers.io())
+ .filter(ok -> ok)
+ .observeOn(Schedulers.io())
+ .doOnNext(v -> TaskBase.enqueue(TaskFunc.UPDATE_CONNECTION))
+ .retry()
+ .subscribe());
+
+ compositeDisposable.add(
+ Observable.combineLatest(patch.observeConnected(), pm.observePatchLifeCycle().distinctUntilChanged(), dateTimeChanged.startWith(new Intent()),
+ (connected, lifeCycle, value) -> (connected && lifeCycle.isActivated()))
+ .subscribeOn(Schedulers.io())
+ .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(it -> onPatchConnected(it))
+ .subscribe());
+
+ compositeDisposable.add(
+ pm.getPatchConfig().observe().doOnNext(config -> {
+ byte[] newKey = config.getSharedKey();
+ patch.updateEncryptionParam(newKey);
+ }).subscribe()
+ );
+
+ compositeDisposable.add(
+ EoPatchRxBus.INSTANCE.listen(EventEoPatchAlarm.class)
+ .filter(it -> it.isFirst())
+ .filter(it -> !pm.getPatchConfig().isDeactivated())
+ .filter(it -> patch.getConnectionState().isConnected())
+ .concatMapIterable(it -> it.getAlarmCodes())
+ .filter(it -> it.isPatchOccurrenceAlert())
+ .flatMap(it -> stopAeBeep(it.getAeCode()).toObservable())
+ .subscribe()
+ );
+
+ compositeDisposable.add(
+ EoPatchRxBus.INSTANCE.listen(EventEoPatchAlarm.class)
+ .filter(it -> it.isFirst())
+ .filter(it -> !pm.getPatchConfig().isDeactivated())
+ .filter(it -> patch.getConnectionState().isConnected())
+ .concatMapIterable(it -> it.getAlarmCodes())
+ .filter(it -> it.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_RESERVIOR_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 == false && activated == true){
+ pm.getPatchConfig().updatetDisconnectedTime();
+ }
+ }
+
+ private void monitorPatchNotification() {
+ compositeDisposable.addAll(
+ patch.observeAlarmNotification()
+ .subscribe(
+ this::onAlarmNotification,
+ throwable -> aapsLogger.error(LTag.PUMP, throwable.getMessage())
+ ),
+ patch.observeInfoNotification()
+ .filter(state -> pm.getPatchConfig().isActivated())
+ .subscribe(
+ this::onInfoNotification,
+ throwable -> aapsLogger.error(LTag.PUMP, throwable.getMessage())
+ )
+ );
+ }
+
+
+ private void onConnectedUpdateSequence() {
+
+ }
+
+ //==============================================================================================
+ // preference database update helper
+ //==============================================================================================
+
+ // synchronized lock
+ private final Object lock = new Object();
+
+ private void updatePatchConfig(Consumer consumer, boolean needSave) throws Exception {
+ synchronized (lock) {
+ consumer.accept(pm.getPatchConfig());
+ if (needSave) {
+ pm.flushPatchConfig();
+ } else {
+ pm.flushPatchConfig();
+ }
+ }
+ }
+
+ synchronized void updateBasal() {
+
+ NormalBasal normalBasal = pm.getNormalBasalManager().getNormalBasal();
+
+ if (normalBasal != null) {
+ // 아래 코드를 실행하면 isDoseUChanged 가 false 가 된다.
+ 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) { //TODO: 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);
+ }
+ });
+ }
+
+
+ //==============================================================================================
+ // IPatchManager interface [NORMAL BASAL]
+ //==============================================================================================
+
+ @Inject
+ StartNormalBasalTask startNormalBasalTask;
+
+ public Single startBasal(NormalBasal basal) {
+
+ return startNormalBasalTask.start(basal, false)
+ .timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ @Inject
+ ResumeBasalTask resumeBasalTask;
+
+ public Single extends BaseResponse> resumeBasal() {
+ return resumeBasalTask.resume()
+ .timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ public Single extends BaseResponse> pauseBasal(float pauseDurationHour) {
+ return pauseBasalImpl(pauseDurationHour, 0, null)
+ .observeOn(SS)
+ .timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ //==============================================================================================
+ // IPatchManager implementation [NORMAL BASAL]
+ //==============================================================================================
+
+ @Inject
+ PauseBasalTask pauseBasalTask;
+
+ private Single extends BaseResponse> pauseBasalImpl(float pauseDurationHour, long alarmOccurredTime, @Nullable AlarmCode alarmCode) {
+ return pauseBasalTask.pause(pauseDurationHour, alarmOccurredTime, alarmCode);
+ }
+
+ private Single stopBasal() {
+ return BASAL_STOP.stop();
+ }
+
+ private void insertBasalStart() throws SQLException {
+ insertBasalStart(System.currentTimeMillis());
+ }
+
+ private void insertBasalStart(long timestamp) throws SQLException {
+ NormalBasal startedBasal = pm.getNormalBasalManager().getNormalBasal();
+ if (startedBasal != null) {
+ startedBasal.updateNormalBasalIndex();
+ pm.flushNormalBasalManager();
+ }
+ }
+
+ //==============================================================================================
+ // IPatchManager interface [TEMP BASAL]
+ //==============================================================================================
+
+ @Inject
+ StartTempBasalTask startTempBasalTask;
+
+ public Single startTempBasal(TempBasal tempBasal) {
+ return startTempBasalTask.start(tempBasal).timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ // 템프베이젤 주입 정지
+ // 템프베이젤이 정지되면 자동으로 노멀베이젤이 활성화된다
+ // 외부에서 호출된다. 즉 명시적으로 tempBasal 정지. 이 때는 normalBasal resume 은 PatchState 보고 처리.
+
+ @Inject
+ StopTempBasalTask stopTempBasalTask;
+
+ public Single stopTempBasal() {
+ return stopTempBasalTask.stop().timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ @Inject
+ StartQuickBolusTask startQuickBolusTask;
+
+ @Inject
+ StartCalcBolusTask startCalcBolusTask;
+
+ @Inject
+ StopComboBolusTask stopComboBolusTask;
+
+ @Inject
+ StopNowBolusTask stopNowBolusTask;
+
+ @Inject
+ StopExtBolusTask stopExtBolusTask;
+
+
+ public Single extends BolusResponse> startQuickBolus(float nowDoseU, float exDoseU,
+ BolusExDuration exDuration) {
+ return startQuickBolusTask.start(nowDoseU, exDoseU, exDuration)
+ .timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ public Single extends BolusResponse> startCalculatorBolus(DetailedBolusInfo detailedBolusInfo) {
+ return startCalcBolusTask.start(detailedBolusInfo)
+ .timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ public Single stopNowBolus() {
+ return stopNowBolusTask.stop().timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ public Single stopExtBolus() {
+ return stopExtBolusTask.stop().timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ public Single stopComboBolus(){
+ return stopComboBolusTask.stop().timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
+ }
+
+ private Single extends BaseResponse> stopNowAndExtBolus() {
+
+ boolean nowActive = pm.getPatchState().isNowBolusActive();
+ boolean extActive = pm.getPatchState().isExtBolusActive();
+
+ if (nowActive && extActive) {
+ return stopComboBolus();
+ } else if (nowActive) {
+ return stopNowBolus();
+ } else if (extActive) {
+ return stopExtBolus();
+ }
+
+ return Single.just(new PatchBooleanResponse(true));
+ }
+
+ //==============================================================================================
+ // IPatchManager implementation [BOLUS]
+ //==============================================================================================
+
+ public void readBolusStatusFromNotification(InfoNotification noti) {
+ if (noti.isBolusRegAct()) {
+ BolusCurrent bolusCurrent = pm.getBolusCurrent();
+
+ Arrays.asList(BolusType.NOW, BolusType.EXT).forEach(type -> {
+ if (noti.isBolusRegAct(type)) { // 완료되었어도 업데이트 필요.
+ int injectedPumpCount = noti.getInjected(type);
+ int remainPumpCount = noti.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);
+ }
+
+ public Single extends BaseResponse> stopAll(){
+ List> sources = new ArrayList<>();
+
+ // 노멀볼루스 또는 확장볼루스가 동작중이면 정지
+
+ if (pm.getPatchState().isNowBolusActive() || pm.getPatchState().isExtBolusActive()) {
+ sources.add(stopNowAndExtBolus());
+ }
+
+ // 템프베이젤이 동작중이면 중지
+ if (pm.getPatchState().isTempBasalActive()) {
+ sources.add(stopTempBasal());
+ }
+
+ sources.add(stopBasal());
+
+ return Single.concat(sources).lastOrError();
+ }
+
+ public Single stopBuzz() {
+ return BUZZER_STOP.stop();
+ }
+
+ @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 Exception {
+ 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 Exception {
+ readBolusStatusFromNotification(notification);
+ updateInjected(notification, false);
+ if (notification.isBolusDone()) {
+ fetchPatchState();
+ }
+ }
+
+ void updateInjected(BaseNotification notification, boolean needSave) throws Exception {
+ 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);
+ KeyPair pair = kpg.generateKeyPair();
+ return pair;
+ });
+ }
+
+ 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,
+ InvalidAlgorithmParameterException {
+ 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, InvalidAlgorithmParameterException {
+ 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) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ //==============================================================================================
+ // Single Scheduler all callback must be observed on
+ //==============================================================================================
+
+ private static final Scheduler SS = Schedulers.single();
+
+ public BleConnectionState getPatchConnectionState() {
+ BleConnectionState result = patch.getConnectionState();
+ return result;
+ }
+
+ public Observable observePatchConnectionState() {
+ return patch.observeConnectionState();
+ }
+
+ public void updateMacAddress(String mac, boolean b){
+ patch.updateMacAddress(mac, b);
+ }
+}
+
+class AlarmFiredEventInfo
+{
+ public AlarmCode code;
+ public long createTimestamp;
+
+ public AlarmFiredEventInfo(AlarmCode code, long createTimestamp) {
+ this.code = code;
+ this.createTimestamp = createTimestamp;
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchStateManager.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PatchStateManager.java
new file mode 100644
index 0000000000..0efaa50521
--- /dev/null
+++ b/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.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.Maybe;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.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
+ public PatchStateManager() {
+
+ }
+
+ public synchronized void updatePatchState(PatchState newState) {
+ Maybe.fromCallable(() -> newState).observeOn(Schedulers.single())
+ .doOnSuccess(patchState -> updatePatchStateInner(patchState))
+ .observeOn(AndroidSchedulers.mainThread())
+ .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) { // TODO 상수로 변경.
+ /* 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(); //normal basal index를 업데이트하여 basal change 이력이 또 발생하는것을 방지(df_356)
+ }
+
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PreferenceManager.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PreferenceManager.kt
new file mode 100644
index 0000000000..97164f6c81
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/PreferenceManager.kt
@@ -0,0 +1,254 @@
+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.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()
+ // mAlarms.clear()
+ // flushAlarms()
+ }
+ }
+
+ }
+
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ActivateTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ActivateTask.java
new file mode 100644
index 0000000000..8e2c3710f1
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ActivateTask.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.core.api.BasalScheduleSetBig;
+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.Single;
+import io.reactivex.schedulers.Schedulers;
+
+@Singleton
+public class ActivateTask extends TaskBase {
+ @Inject StartNormalBasalTask startBasalTask;
+
+ private SetKey SET_KEY;
+ private BasalScheduleSetBig BASAL_SCHEDULE_SET_BIG;
+
+
+ @Inject
+ public ActivateTask() {
+ super(TaskFunc.ACTIVATE);
+ SET_KEY = new SetKey();
+ BASAL_SCHEDULE_SET_BIG = new BasalScheduleSetBig();
+ }
+
+ public Single start() {
+ NormalBasal enabled = pm.getNormalBasalManager().getNormalBasal();
+ return isReady()
+ .concatMapSingle(v -> SET_KEY.setKey())
+ .doOnNext(this::checkResponse)
+ .firstOrError()
+ .observeOn(Schedulers.io()).doOnSuccess(this::onActivated)
+ .flatMap(v -> startBasalTask.start(enabled, false))
+ .map(BaseResponse::isSuccess)
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, e.getMessage()));
+ }
+
+ private void onActivated(BaseResponse response) {
+ pm.updatePatchLifeCycle(PatchLifecycleEvent.createActivated());
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/BolusTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/BolusTask.java
new file mode 100644
index 0000000000..016ea308a6
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/BolusTask.java
@@ -0,0 +1,124 @@
+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.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; // dm_1720
+ long endTimestamp = startTimestamp + getPumpDuration(nowDoseU);
+
+ long nowHistoryID = 1L; //record no
+ long exStartTimestamp = 0;
+
+ if (now) {
+ pm.getBolusCurrent().startNowBolus(nowHistoryID, nowDoseU, startTimestamp, endTimestamp);
+ }
+ if (ext) {
+ long estimatedExStartTimestamp = 0;
+
+ 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, float correctionBolus, float exDoseU,
+ BolusExDuration exDuration) {
+
+ boolean now = (nowDoseU > 0);
+ boolean ext = (exDoseU > 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);
+ }
+
+ long exStartTimestamp = 0;
+
+ if (ext) {
+ long estimatedExStartTimestamp = 0;
+
+ 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 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.INSTANCE.getFLOOR2_BOLUS().apply(injected * AppConstant.INSULIN_UNIT_P);
+ 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.INSTANCE.getFLOOR2_BOLUS().apply(injected * AppConstant.INSULIN_UNIT_P);
+ bolusCurrent.setEndTimeSynced(BolusType.EXT, true);
+ pm.flushBolusCurrent();
+ }
+ }
+
+ private long getPumpDuration(float doseU) {
+ if (doseU > 0) {
+ long pumpDuration = pm.getPatchConfig().getPumpDurationSmallMilli(); //todo
+ return (long) ((doseU / AppConstant.Companion.getBOLUS_UNIT_STEP()) * pumpDuration);
+ }
+ return 0L;
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/DeactivateTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/DeactivateTask.java
new file mode 100644
index 0000000000..1299a99a6b
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/DeactivateTask.java
@@ -0,0 +1,132 @@
+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.code.BolusType;
+import info.nightscout.androidaps.plugins.pump.eopatch.code.DeactivationStatus;
+
+import java.sql.SQLException;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.DeActivation;
+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.PatchState;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasal;
+import io.reactivex.Observable;
+import io.reactivex.Single;
+import io.reactivex.schedulers.Schedulers;
+
+@Singleton
+public class DeactivateTask extends TaskBase {
+
+ @Inject
+ StopBasalTask stopBasalTask;
+
+ @Inject
+ IPreferenceManager pm;
+
+ private 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(Schedulers.io())
+ .doOnSuccess(response -> onDeactivated(false)))
+ .map(response -> DeactivationStatus.of(response.isSuccess(), forced))
+ .firstOrError()
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, e.getMessage()))
+ .onErrorResumeNext(e -> {
+ if (forced) {
+ try {
+ onDeactivated(true);
+ } catch (Exception t) {
+ aapsLogger.error(LTag.PUMPCOMM, e.getMessage());
+ }
+ }
+
+ return Single.just(DeactivationStatus.of(false, forced));
+ });
+ }
+
+ private Observable isReadyCheckActivated() {
+ if (pm.getPatchConfig().isActivated()) {
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+
+ stopBasalTask.enqueue();
+
+ return isReady2();
+ }
+
+ return isReady();
+ }
+
+ /* Schedulers.io() */
+ private void onDeactivated(boolean forced) throws SQLException {
+ synchronized (lock) {
+ patch.updateMacAddress(null, false);
+
+ if (pm.getPatchConfig().getLifecycleEvent().isShutdown()) {
+ return;
+ }
+
+ cleanUpRepository();
+
+ pm.getNormalBasalManager().updateForDeactivation();
+
+ pm.updatePatchLifeCycle(PatchLifecycleEvent.createShutdown());
+
+ }
+ }
+
+ private void cleanUpRepository() throws SQLException {
+ updateNowBolusStopped();
+ updateExtBolusStopped();
+ updateTempBasalStopped();
+ }
+
+ private void updateTempBasalStopped() throws SQLException {
+ TempBasal tempBasal = pm.getTempBasalManager().getStartedBasal();
+
+ if (tempBasal != null) {
+ pm.getTempBasalManager().updateBasalStopped();
+ pm.flushTempBasalManager();
+ }
+ }
+
+ /* copied from BolusTask. */
+ 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();
+ }
+ }
+
+ /* copied from BolusTask. */
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/FetchAlarmTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/FetchAlarmTask.java
new file mode 100644
index 0000000000..13a9326480
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/FetchAlarmTask.java
@@ -0,0 +1,53 @@
+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.AlarmCode;
+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.Single;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+
+@Singleton
+public class FetchAlarmTask extends TaskBase {
+
+ @Inject
+ RxBus rxBus;
+
+ @Inject
+ IAlarmRegistry alarmRegistry;
+
+ private 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()));
+ }
+
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/GetPatchInfoTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/GetPatchInfoTask.java
new file mode 100644
index 0000000000..e84f9d649f
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/GetPatchInfoTask.java
@@ -0,0 +1,116 @@
+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.Single;
+import io.reactivex.schedulers.Schedulers;
+
+@Singleton
+public class GetPatchInfoTask extends TaskBase {
+
+ @Inject
+ UpdateConnectionTask updateConnectionTask;
+
+ private SetGlobalTime SET_GLOBAL_TIME;
+ private GetSerialNumber SERIAL_NUMBER_GET;
+ private GetLOT LOT_NUMBER_GET;
+ private GetFirmwareVersion FIRMWARE_VERSION_GET;
+ private GetWakeUpTime WAKE_UP_TIME_GET;
+ private GetPumpDuration PUMP_DURATION_GET;
+ private 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) // fail 시 false 가 아래로 내려간다.
+ .first(true);
+
+ return isReady()
+ .concatMapSingle(it -> tasks)
+ .firstOrError()
+// .flatMap(v -> updateConnectionTask.update()).map(v -> true)
+ .observeOn(Schedulers.io())
+ .doOnSuccess(this::onPatchWakeupSuccess)
+ .doOnError(this::onPatchWakeupFailed)
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, e.getMessage()));
+ }
+
+ 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() * 100); // 0.1 초 단위
+ pm.getPatchConfig().setPumpDurationMediumMilli(v.getDurationM() * 100);
+ pm.getPatchConfig().setPumpDurationSmallMilli(v.getDurationS() * 100);
+ }
+
+ private void onModelNameResponse(ModelNameResponse modelNameResponse) {
+ pm.getPatchConfig().setPatchModelName(modelNameResponse.getModelName());
+ }
+
+ /* Schedulers.io() */
+ private void onPatchWakeupSuccess(Boolean result) {
+ synchronized (lock) {
+ pm.flushPatchConfig();
+ }
+ }
+
+ /* Schedulers.io() */
+ private void onPatchWakeupFailed(Throwable e) {
+ patch.setSeq(-1);
+ pm.getPatchConfig().updateDeactivated();
+ pm.flushPatchConfig();
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/InfoReminderTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/InfoReminderTask.java
new file mode 100644
index 0000000000..f2a32536e8
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/InfoReminderTask.java
@@ -0,0 +1,48 @@
+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.Single;
+
+@Singleton
+public class InfoReminderTask extends TaskBase {
+
+ @Inject
+ IPreferenceManager pm;
+
+ private 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()));
+ }
+
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/InternalSuspendedTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/InternalSuspendedTask.java
new file mode 100644
index 0000000000..9e73ead46e
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/InternalSuspendedTask.java
@@ -0,0 +1,183 @@
+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.core.define.IPatchConstant;
+
+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.BolusStop;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetInternalSuspendTime;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.TempBasalScheduleStop;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchInternalSuspendTimeResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
+import info.nightscout.androidaps.queue.Callback;
+import info.nightscout.androidaps.queue.commands.Command;
+
+import io.reactivex.Observable;
+import io.reactivex.Single;
+import io.reactivex.subjects.BehaviorSubject;
+
+@Singleton
+public class InternalSuspendedTask extends BolusTask {
+
+ @Inject CommandQueue commandQueue;
+ @Inject AAPSLogger aapsLogger;
+ @Inject PumpSync pumpSync;
+ @Inject UserEntryLogger uel;
+
+ private GetInternalSuspendTime INTERNAL_SUSPEND_TIME_GET;
+ private BolusStop BOLUS_STOP;
+ private TempBasalScheduleStop TEMP_BASAL_SCHEDULE_STOP;
+ private BehaviorSubject bolusCheckSubject = BehaviorSubject.create();
+ private BehaviorSubject exbolusCheckSubject = BehaviorSubject.create();
+ private BehaviorSubject basalCheckSubject = BehaviorSubject.create();
+
+ @Inject
+ public InternalSuspendedTask() {
+ super(TaskFunc.INTERNAL_SUSPEND);
+
+ INTERNAL_SUSPEND_TIME_GET = new GetInternalSuspendTime();
+ BOLUS_STOP = new BolusStop();
+ TEMP_BASAL_SCHEDULE_STOP = new TempBasalScheduleStop();
+ }
+
+ private Observable getBolusSebject(){
+ return bolusCheckSubject.hide();
+ }
+
+ private Observable getExbolusSebject(){
+ return exbolusCheckSubject.hide();
+ }
+
+ private Observable getBasalSebject(){
+ return basalCheckSubject.hide();
+ }
+
+ public Single start(boolean isNowBolusActive, boolean isExtBolusActive, boolean isTempBasalActive) {
+ PatchState patchState = pm.getPatchState();
+
+ if (isNowBolusActive || isExtBolusActive) {
+ enqueue(TaskFunc.READ_BOLUS_FINISH_TIME);
+ }
+
+ if (commandQueue.isRunning(Command.CommandType.BOLUS)) {
+ uel.log(UserEntryMapper.Action.CANCEL_BOLUS, UserEntryMapper.Sources.EOPatch2);
+ commandQueue.cancelAllBoluses();
+ 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) -> {
+ return (bolusReady && exbolusReady && basalReady);
+ })
+ .filter(ready -> ready)
+ .flatMap(v -> isReady())
+ .concatMapSingle(v -> getInternalSuspendTime())
+ .firstOrError()
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, e.getMessage()));
+ }
+
+ private Single getInternalSuspendTime() {
+ return INTERNAL_SUSPEND_TIME_GET.get()
+ .doOnSuccess(this::checkResponse)
+ .map(PatchInternalSuspendTimeResponse::getTotalSeconds);
+ }
+
+ private Single stopNowBolus(long suspendTime, boolean isNowBolusActive) {
+ if (isNowBolusActive) {
+ long suspendedTimestamp = pm.getPatchConfig().getPatchWakeupTimestamp() + suspendTime;
+
+ return BOLUS_STOP.stop(IPatchConstant.NOW_BOLUS_ID)
+ .doOnSuccess(this::checkResponse)
+ .doOnSuccess(v -> onNowBolusStopped(v.getInjectedBolusAmount(), suspendedTimestamp))
+ .map(v -> suspendTime);
+ }
+
+ return Single.just(suspendTime);
+ }
+
+ private Single stopExtBolus(long suspendTime, boolean isExtBolusActive) {
+ if (isExtBolusActive) {
+ long suspendedTimestamp = pm.getPatchConfig().getPatchWakeupTimestamp() + suspendTime;
+
+ return BOLUS_STOP.stop(IPatchConstant.EXT_BOLUS_ID)
+ .doOnSuccess(this::checkResponse)
+ .doOnSuccess(v -> onExtBolusStopped(v.getInjectedBolusAmount(), suspendedTimestamp))
+ .map(v -> suspendTime);
+ }
+
+ return Single.just(suspendTime);
+ }
+
+ private Single stopTempBasal(long suspendTime, boolean isTempBasalActive) {
+ if (isTempBasalActive) {
+ return TEMP_BASAL_SCHEDULE_STOP.stop()
+ .doOnSuccess(this::checkResponse)
+ .doOnSuccess(v -> onTempBasalCanceled())
+ .map(v -> suspendTime);
+ }
+
+ return Single.just(suspendTime);
+ }
+
+ private void onNowBolusStopped(int injectedBolusAmount, long suspendedTimestamp) {
+ updateNowBolusStopped(injectedBolusAmount, suspendedTimestamp);
+ }
+
+ private void onExtBolusStopped(int injectedBolusAmount, long suspendedTimestamp) {
+ updateExtBolusStopped(injectedBolusAmount, suspendedTimestamp);
+ }
+
+ private void onTempBasalCanceled() {
+ pm.getTempBasalManager().updateBasalStopped();
+ pm.flushTempBasalManager();
+ }
+
+ 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);
+ exbolusCheckSubject.onNext(false);
+ basalCheckSubject.onNext(false);
+ });
+ }
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/NeedleSensingTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/NeedleSensingTask.java
new file mode 100644
index 0000000000..d924bc3659
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/NeedleSensingTask.java
@@ -0,0 +1,51 @@
+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.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()));
+ }
+
+ private void onResponse(PatchState v) {
+ if (v.isNeedNeedleSensing()) {
+ alarmRegistry.add(AlarmCode.A016, 0, false).subscribe();
+ } else {
+ alarmRegistry.remove(AlarmCode.A016).subscribe();
+ }
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/PauseBasalTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/PauseBasalTask.java
new file mode 100644
index 0000000000..ebd32d3aec
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/PauseBasalTask.java
@@ -0,0 +1,225 @@
+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.core.define.IPatchConstant;
+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.api.BolusStop;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.TempBasalScheduleStop;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasal;
+import info.nightscout.androidaps.queue.Callback;
+import info.nightscout.androidaps.queue.commands.Command;
+import io.reactivex.Observable;
+import io.reactivex.Single;
+import io.reactivex.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 BasalPause BASAL_PAUSE;
+ private BolusStop BOLUS_STOP;
+ private TempBasalScheduleStop TEMP_BASAL_SCHEDULE_STOP;
+
+ private BehaviorSubject bolusCheckSubject = BehaviorSubject.create();
+ private BehaviorSubject exbolusCheckSubject = BehaviorSubject.create();
+ private BehaviorSubject basalCheckSubject = BehaviorSubject.create();
+
+
+ @Inject
+ public PauseBasalTask() {
+ super(TaskFunc.PAUSE_BASAL);
+
+ BASAL_PAUSE = new BasalPause();
+ BOLUS_STOP = new BolusStop();
+ TEMP_BASAL_SCHEDULE_STOP = new TempBasalScheduleStop();
+ }
+
+ private Observable getBolusSebject(){
+ return bolusCheckSubject.hide();
+ }
+
+ private Observable getExbolusSebject(){
+ return exbolusCheckSubject.hide();
+ }
+
+ private Observable getBasalSebject(){
+ 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();
+ 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) -> {
+ return (bolusReady && exbolusReady && basalReady);
+ })
+ .filter(ready -> ready)
+ .flatMap(v -> isReady())
+ .concatMapSingle(v -> getSuspendedTime(pausedTimestamp, alarmCode))
+ .concatMapSingle(suspendedTimestamp -> pauseBasal(pauseDurationHour, suspendedTimestamp, alarmCode))
+ .firstOrError()
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, e.getMessage()));
+ }
+
+ private Single getSuspendedTime(long pausedTimestamp, @Nullable AlarmCode alarmCode) {
+ return Single.just(pausedTimestamp);
+ }
+
+ private Single stopNowBolus(long pausedTimestamp, boolean isNowBolusActive) {
+ if (isNowBolusActive) {
+ return BOLUS_STOP.stop(IPatchConstant.NOW_BOLUS_ID)
+ .doOnSuccess(this::checkResponse)
+ .doOnSuccess(v -> onNowBolusStopped(v.getInjectedBolusAmount(), pausedTimestamp))
+ .map(v -> pausedTimestamp);
+ }
+
+ return Single.just(pausedTimestamp);
+ }
+
+ private Single stopExtBolus(long pausedTimestamp, boolean isExtBolusActive) {
+ if (isExtBolusActive) {
+ return BOLUS_STOP.stop(IPatchConstant.EXT_BOLUS_ID)
+ .doOnSuccess(this::checkResponse)
+ .doOnSuccess(v -> onExtBolusStopped(v.getInjectedBolusAmount(), pausedTimestamp))
+ .map(v -> pausedTimestamp);
+ }
+
+ return Single.just(pausedTimestamp);
+ }
+
+ private Single stopTempBasal(long pausedTimestamp, boolean isTempBasalActive) {
+ if (isTempBasalActive) {
+ return TEMP_BASAL_SCHEDULE_STOP.stop()
+ .doOnSuccess(this::checkResponse)
+ .doOnSuccess(v -> onTempBasalCanceled(pausedTimestamp))
+ .map(v -> pausedTimestamp);
+ }
+
+ return Single.just(pausedTimestamp);
+ }
+
+ private Single pauseBasal(float pauseDurationHour, long suspendedTimestamp, @Nullable AlarmCode alarmCode) {
+ if(alarmCode == null) {
+ return BASAL_PAUSE.pause(pauseDurationHour)
+ .doOnSuccess(this::checkResponse)
+ .doOnSuccess(v -> onBasalPaused(pauseDurationHour, suspendedTimestamp, null));
+ }
+
+ // 정지 알람 발생 시 basal pause 커맨드 전달하지 않음 - 주입 정지 이력만 생성
+ onBasalPaused(pauseDurationHour, suspendedTimestamp, alarmCode);
+
+ return Single.just(new PatchBooleanResponse(true));
+ }
+
+ private void onBasalPaused(float pauseDurationHour, long suspendedTimestamp, @Nullable AlarmCode alarmCode) {
+ if (!pm.getNormalBasalManager().isSuspended()) {
+ String strCode = (alarmCode != null) ? alarmCode.name() : null;
+
+ 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);
+ }
+
+ private void onNowBolusStopped(int injectedBolusAmount, long suspendedTimestamp) {
+ updateNowBolusStopped(injectedBolusAmount, suspendedTimestamp);
+ }
+
+ private void onExtBolusStopped(int injectedBolusAmount, long suspendedTimestamp) {
+ updateExtBolusStopped(injectedBolusAmount, suspendedTimestamp);
+ }
+
+ private void onTempBasalCanceled(long suspendedTimestamp) {
+ TempBasal tempBasal = pm.getTempBasalManager().getStartedBasal();
+
+ if (tempBasal != null) {
+ pm.getTempBasalManager().updateBasalStopped();
+ pm.flushTempBasalManager();
+ }
+ }
+
+ 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);
+ exbolusCheckSubject.onNext(false);
+ basalCheckSubject.onNext(false);
+ });
+ }
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/PrimingTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/PrimingTask.java
new file mode 100644
index 0000000000..57d318ac67
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/PrimingTask.java
@@ -0,0 +1,55 @@
+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.Observable;
+
+@Singleton
+public class PrimingTask extends TaskBase {
+
+ private UpdateConnection UPDATE_CONNECTION;
+ private 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()));
+ }
+
+ private Observable observePrimingSuccess(long count) {
+
+ return Observable.merge(
+ Observable.interval(1, TimeUnit.SECONDS).take(count + 10)
+ .map(v -> v * 3) // 현재 20초 니깐 60 정도에서 꽉 채워짐. *4 도 괜찮을 듯.
+ .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 -> patchState.isPrimingSuccess())
+ .map(result -> count) // 프라이밍 체크 용 성공시 count 값 리턴
+ );
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ReadBolusFinishTimeTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ReadBolusFinishTimeTask.java
new file mode 100644
index 0000000000..6f9d1a212a
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ReadBolusFinishTimeTask.java
@@ -0,0 +1,64 @@
+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.Single;
+
+@Singleton
+public class ReadBolusFinishTimeTask extends BolusTask {
+
+ private 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()));
+ }
+
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ReadTempBasalFinishTimeTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ReadTempBasalFinishTimeTask.java
new file mode 100644
index 0000000000..0d42506fa0
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ReadTempBasalFinishTimeTask.java
@@ -0,0 +1,46 @@
+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.Single;
+
+@Singleton
+public class ReadTempBasalFinishTimeTask extends TaskBase {
+
+ private 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()));
+ }
+
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ResumeBasalTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ResumeBasalTask.java
new file mode 100644
index 0000000000..a25febb404
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/ResumeBasalTask.java
@@ -0,0 +1,64 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.shared.logging.AAPSLogger;
+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.Single;
+
+@Singleton
+public class ResumeBasalTask extends TaskBase {
+ @Inject
+ StartNormalBasalTask startNormalBasalTask;
+
+ @Inject
+ PatchStateManager patchStateManager;
+
+ private BasalResume BASAL_RESUME;
+
+ @Inject
+ public ResumeBasalTask() {
+ super(TaskFunc.RESUME_BASAL);
+ BASAL_RESUME = new BasalResume();
+ }
+
+ public synchronized Single extends BaseResponse> resume() {
+
+ if (pm.getPatchConfig().getNeedSetBasalSchedule()) {
+ NormalBasal normalBasal = pm.getNormalBasalManager().getNormalBasal();
+
+ if (normalBasal != null) {
+ return startNormalBasalTask.start(normalBasal, true);
+ }
+ }
+
+ return isReady().concatMapSingle(v -> BASAL_RESUME.resume())
+ .doOnNext(this::checkResponse)
+ .firstOrError()
+ .doOnSuccess(v -> onResumeResponse(v))
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, e.getMessage()));
+ }
+
+ private void onResumeResponse(PatchBooleanResponse v) throws SQLException {
+ if (v.isSuccess()) {
+ patchStateManager.onBasalResumed(v.getTimestamp() + 1000);
+ }
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+ }
+
+ @Override
+ protected void preCondition() throws Exception {
+ checkPatchActivated();
+ checkPatchConnected();
+ }
+
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SelfTestTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SelfTestTask.java
new file mode 100644
index 0000000000..cafe9e1911
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SelfTestTask.java
@@ -0,0 +1,67 @@
+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.Single;
+
+@Singleton
+public class SelfTestTask extends TaskBase {
+
+ private GetTemperature TEMPERATURE_GET;
+ private GetVoltageLevelB4Priming BATTERY_LEVEL_GET_BEFORE_PRIMING;
+ private 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)
+ .doOnSuccess(this::onTemperatureResult),
+ BATTERY_LEVEL_GET_BEFORE_PRIMING.get()
+ .map(BatteryVoltageLevelPairingResponse::getResult)
+ .doOnSuccess(this::onBatteryResult),
+ GET_GLOBAL_TIME.get(false)
+ .map(GlobalTimeResponse::getResult)
+ .doOnSuccess(this::onTimeResult)))
+ .filter(result -> result != PatchSelfTestResult.TEST_SUCCESS)
+ .first(PatchSelfTestResult.TEST_SUCCESS);
+
+ return isReady()
+ .concatMapSingle(v -> tasks)
+ .firstOrError()
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, e.getMessage()));
+ }
+
+ private void onTemperatureResult(PatchSelfTestResult patchSelfTestResult) {
+ if (patchSelfTestResult != PatchSelfTestResult.TEST_SUCCESS) {
+ }
+ }
+
+ private void onBatteryResult(PatchSelfTestResult patchSelfTestResult) {
+ if (patchSelfTestResult != PatchSelfTestResult.TEST_SUCCESS) {
+ }
+ }
+
+ private void onTimeResult(PatchSelfTestResult patchSelfTestResult) {
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SetGlobalTimeTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SetGlobalTimeTask.java
new file mode 100644
index 0000000000..0626764c8c
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SetGlobalTimeTask.java
@@ -0,0 +1,75 @@
+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.Single;
+
+@Singleton
+public class SetGlobalTimeTask extends TaskBase {
+
+ private SetGlobalTime SET_GLOBAL_TIME;
+ private 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()));
+ }
+
+ private boolean 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);
+ // TimeZoneOffset (8bit / signed): 타임존 offset 15분 단위을 1로 환산, Korea 의 경우 36값(+9:00)
+ 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 true;
+ }
+
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SetLowReservoirTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SetLowReservoirTask.java
new file mode 100644
index 0000000000..14fd2510bb
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SetLowReservoirTask.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.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.Single;
+
+@Singleton
+public class SetLowReservoirTask extends TaskBase {
+
+ @Inject
+ IPreferenceManager pm;
+
+ private 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()));
+ }
+
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartBondTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartBondTask.java
new file mode 100644
index 0000000000..4b4e9e02ef
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartBondTask.java
@@ -0,0 +1,58 @@
+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.Single;
+
+import static info.nightscout.androidaps.plugins.pump.eopatch.core.api.StartBonding.OPTION_NUMERIC;
+
+/**
+ * (주의) API 호출 후 본딩을 위해서 밑단 연결이 끊어짐.
+ */
+@Singleton
+public class StartBondTask extends TaskBase {
+ private 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());
+ })
+ .firstOrError();
+ }
+
+ private synchronized void prefSetMacAddress(String mac) {
+ pm.getPatchConfig().setMacAddress(mac);
+ }
+}
+
+
+
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartCalcBolusTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartCalcBolusTask.java
new file mode 100644
index 0000000000..6cdbb99329
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartCalcBolusTask.java
@@ -0,0 +1,62 @@
+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.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.Single;
+
+@Singleton
+public class StartCalcBolusTask extends BolusTask {
+
+ private BolusStart NOW_BOLUS_START;
+ private ExtBolusStart EXT_BOLUS_START;
+ private ComboBolusStart COMBO_BOLUS_START;
+
+ @Inject
+ public StartCalcBolusTask() {
+ super(TaskFunc.START_CALC_BOLUS);
+
+ NOW_BOLUS_START = new BolusStart();
+ EXT_BOLUS_START = new ExtBolusStart();
+ COMBO_BOLUS_START = new ComboBolusStart();
+ }
+
+ public Single extends BolusResponse> start(DetailedBolusInfo detailedBolusInfo) {
+ return isReady().concatMapSingle(v -> startBolusImpl((float)detailedBolusInfo.insulin, 0f, BolusExDuration.OFF))
+ .doOnNext(this::checkResponse)
+ .firstOrError()
+ .doOnSuccess(v -> onSuccess((float)detailedBolusInfo.insulin, (float)detailedBolusInfo.insulin, 0f, BolusExDuration.OFF))
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, e.getMessage()));
+ }
+
+ private Single extends BolusResponse> startBolusImpl(float nowDoseU, float exDoseU,
+ BolusExDuration exDuration) {
+ if (nowDoseU > 0 && exDoseU > 0) {
+ return COMBO_BOLUS_START.start(nowDoseU, exDoseU, exDuration.getMinute());
+ } else if (exDoseU > 0) {
+ return EXT_BOLUS_START.start(exDoseU, exDuration.getMinute());
+ } else {
+ return NOW_BOLUS_START.start(nowDoseU);
+ }
+ }
+
+ private void onSuccess(float nowDoseU, float correctionBolus, float exDoseU, BolusExDuration exDuration) {
+ onCalcBolusStarted(nowDoseU, correctionBolus, exDoseU, exDuration);
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+ }
+
+ @Override
+ protected void preCondition() throws Exception {
+ //checkPatchActivated();
+ checkPatchConnected();
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartNormalBasalTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartNormalBasalTask.java
new file mode 100644
index 0000000000..d7a232e14b
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartNormalBasalTask.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.core.api.BasalScheduleSetBig;
+
+import java.sql.SQLException;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BasalScheduleSetResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.NormalBasal;
+import io.reactivex.Single;
+import io.reactivex.schedulers.Schedulers;
+
+@Singleton
+public class StartNormalBasalTask extends TaskBase {
+
+ private BasalScheduleSetBig BASAL_SCHEDULE_SET_BIG;
+
+ @Inject
+ PatchStateManager patchStateManager;
+
+ @Inject
+ public StartNormalBasalTask() {
+ super(TaskFunc.START_NORMAL_BASAL);
+ BASAL_SCHEDULE_SET_BIG = new BasalScheduleSetBig();
+ }
+
+ public Single start(NormalBasal basal, boolean resume) {
+ return isReady().concatMapSingle(v -> startJob(basal, resume)).firstOrError();
+ }
+
+ public Single startJob(NormalBasal basal, boolean resume) {
+ return BASAL_SCHEDULE_SET_BIG.set(basal.getDoseUnitPerSegmentArray())
+ .doOnSuccess(this::checkResponse)
+ .observeOn(Schedulers.io())
+ .doOnSuccess(v -> onStartNormalBasalResponse(v, basal, resume))
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, e.getMessage()));
+ }
+
+ private void onStartNormalBasalResponse(BasalScheduleSetResponse response,
+ NormalBasal basal, boolean resume) throws SQLException {
+
+ long timeStamp = response.getTimestamp();
+ patchStateManager.onBasalStarted(basal, timeStamp+1000);
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+ }
+
+ @Override
+ protected void preCondition() throws Exception {
+ checkPatchConnected();
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartQuickBolusTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartQuickBolusTask.java
new file mode 100644
index 0000000000..87967f8da2
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartQuickBolusTask.java
@@ -0,0 +1,62 @@
+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.Single;
+
+@Singleton
+public class StartQuickBolusTask extends BolusTask {
+
+ private BolusStart NOW_BOLUS_START;
+ private ExtBolusStart EXT_BOLUS_START;
+ private ComboBolusStart COMBO_BOLUS_START;
+
+ @Inject
+ public StartQuickBolusTask() {
+ super(TaskFunc.START_QUICK_BOLUS);
+
+ NOW_BOLUS_START = new BolusStart();
+ EXT_BOLUS_START = new ExtBolusStart();
+ COMBO_BOLUS_START = new ComboBolusStart();
+ }
+
+ public Single extends BolusResponse> start(float nowDoseU, float exDoseU,
+ BolusExDuration exDuration) {
+ return isReady().concatMapSingle(v -> startBolusImpl(nowDoseU, exDoseU, exDuration))
+ .doOnNext(this::checkResponse)
+ .firstOrError()
+ .doOnSuccess(v -> onSuccess(nowDoseU, exDoseU, exDuration))
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, e.getMessage()));
+ }
+
+ private Single extends BolusResponse> startBolusImpl(float nowDoseU, float exDoseU,
+ BolusExDuration exDuration) {
+ if (nowDoseU > 0 && exDoseU > 0) {
+ return COMBO_BOLUS_START.start(nowDoseU, exDoseU, exDuration.getMinute());
+ } else if (exDoseU > 0) {
+ return EXT_BOLUS_START.start(exDoseU, exDuration.getMinute());
+ } else {
+ return NOW_BOLUS_START.start(nowDoseU);
+ }
+ }
+
+ private void onSuccess(float nowDoseU, float exDoseU, BolusExDuration exDuration) {
+ onQuickBolusStarted(nowDoseU, exDoseU, exDuration);
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+ }
+
+ @Override
+ protected void preCondition() throws Exception {
+ //checkPatchActivated();
+ checkPatchConnected();
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartTempBasalTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartTempBasalTask.java
new file mode 100644
index 0000000000..e3d6a4fb72
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StartTempBasalTask.java
@@ -0,0 +1,44 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
+
+import info.nightscout.shared.logging.LTag;
+import info.nightscout.androidaps.plugins.pump.eopatch.core.api.TempBasalScheduleStart;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TempBasalScheduleSetResponse;
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasal;
+import io.reactivex.Single;
+import io.reactivex.schedulers.Schedulers;
+
+@Singleton
+public class StartTempBasalTask extends TaskBase {
+
+ private 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(Schedulers.io())
+ .doOnSuccess(v -> onTempBasalStarted(tempBasal))
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, e.getMessage()));
+ }
+
+ private void onTempBasalStarted(TempBasal tempBasal) {
+ enqueue(TaskFunc.UPDATE_CONNECTION);
+ }
+
+ @Override
+ protected void preCondition() throws Exception {
+ checkPatchConnected();
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopBasalTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopBasalTask.java
new file mode 100644
index 0000000000..ff8fa8996f
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopBasalTask.java
@@ -0,0 +1,126 @@
+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.Observable;
+import io.reactivex.Single;
+import io.reactivex.subjects.BehaviorSubject;
+
+@Singleton
+public class StopBasalTask extends TaskBase {
+
+ @Inject IPreferenceManager pm;
+ @Inject CommandQueue commandQueue;
+ @Inject AAPSLogger aapsLogger;
+ @Inject PumpSync pumpSync;
+ @Inject UserEntryLogger uel;
+
+ private BasalStop BASAL_STOP;
+ private BehaviorSubject bolusCheckSubject = BehaviorSubject.create();
+ private BehaviorSubject exbolusCheckSubject = BehaviorSubject.create();
+ private 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();
+ 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) -> {
+ return (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()));
+ }
+
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopComboBolusTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopComboBolusTask.java
new file mode 100644
index 0000000000..fec4f7ba8c
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopComboBolusTask.java
@@ -0,0 +1,85 @@
+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.Single;
+
+@Singleton
+public class StopComboBolusTask extends BolusTask {
+
+ private 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()));
+ }
+
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopExtBolusTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopExtBolusTask.java
new file mode 100644
index 0000000000..f7a44ed3d8
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopExtBolusTask.java
@@ -0,0 +1,59 @@
+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.Single;
+
+@Singleton
+public class StopExtBolusTask extends BolusTask {
+
+ private 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()));
+ }
+
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopNowBolusTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopNowBolusTask.java
new file mode 100644
index 0000000000..28c7e5ad95
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopNowBolusTask.java
@@ -0,0 +1,60 @@
+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.Single;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+
+@Singleton
+public class StopNowBolusTask extends BolusTask {
+
+ private BolusStop BOLUS_STOP;
+
+ @Inject
+ public StopNowBolusTask() {
+ super(TaskFunc.STOP_NOW_BOLUS);
+
+ BOLUS_STOP = new BolusStop();
+ }
+
+ public Single stop() {
+ return isReady()
+ .observeOn(AndroidSchedulers.mainThread())
+ .concatMapSingle(v -> stopJob()).firstOrError()
+ .doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, e.getMessage()));
+ }
+
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopTempBasalTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopTempBasalTask.java
new file mode 100644
index 0000000000..691c7f27c1
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/StopTempBasalTask.java
@@ -0,0 +1,56 @@
+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.Single;
+
+@Singleton
+public class StopTempBasalTask extends TaskBase {
+
+ private 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()));
+ }
+
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SyncBasalHistoryTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SyncBasalHistoryTask.java
new file mode 100644
index 0000000000..d66ea87d82
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/SyncBasalHistoryTask.java
@@ -0,0 +1,151 @@
+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.sql.SQLException;
+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.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 BasalHistoryIndexGet BASAL_HISTORY_INDEX_GET;
+ private BasalHistoryGetExBig BASAL_HISTORY_GET_EX_BIG;
+ private 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); // 베이젤 싱크 사용 안함
+// return isReady()
+// .concatMapSingle(v -> syncBoth(pm.getPatchConfig().getLastIndex(), end))
+// .firstOrError()
+// .doOnSuccess(this::updatePatchLastIndex);
+ }
+
+ public Single sync() {
+ return Single.just(1); // 베이젤 싱크 사용 안함
+// return isReady()
+// .concatMapSingle(v -> getLastIndex())
+// .concatMapSingle(end -> syncBoth(pm.getPatchConfig().getLastIndex(), end))
+// .firstOrError()
+// .doOnSuccess(this::updatePatchLastIndex);
+ }
+
+ 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) throws SQLException {
+
+ 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) throws SQLException {
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/TaskBase.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/TaskBase.java
new file mode 100644
index 0000000000..b92163309b
--- /dev/null
+++ b/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.Observable;
+import io.reactivex.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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/TaskFunc.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/TaskFunc.java
new file mode 100644
index 0000000000..2e1f8f77b8
--- /dev/null
+++ b/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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/TaskQueue.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/TaskQueue.java
new file mode 100644
index 0000000000..8b9ef9e8ab
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/TaskQueue.java
@@ -0,0 +1,115 @@
+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.shared.logging.AAPSLogger;
+import info.nightscout.shared.logging.LTag;
+import io.reactivex.Observable;
+import io.reactivex.schedulers.Schedulers;
+import io.reactivex.subjects.BehaviorSubject;
+
+@Singleton
+public class TaskQueue {
+ @Inject AAPSLogger aapsLogger;
+
+ 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 -> {
+ return 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(Schedulers.io())
+ .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 itor = queue.iterator();
+
+ /* remove 1st queue */
+ itor.next();
+
+ while (itor.hasNext()) {
+ PatchTask item = itor.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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/UpdateConnectionTask.java b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/UpdateConnectionTask.java
new file mode 100644
index 0000000000..729bbb4651
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/ble/task/UpdateConnectionTask.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.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.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()));
+ }
+
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/AlarmCategory.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/AlarmCategory.kt
new file mode 100644
index 0000000000..10d3115a01
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/AlarmCategory.kt
@@ -0,0 +1,46 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+import com.google.android.gms.common.internal.Preconditions
+
+enum class AlarmCategory private constructor(val rawValue: Int) {
+ NONE(0),
+ ALARM(1),
+ ALERT(2);
+
+ companion object {
+
+
+ /**
+ * rawValue로 값을 찾기, 못찾으면 null을 리턴
+ */
+ fun ofRaw(rawValue: Int?): AlarmCategory? {
+ if (rawValue == null) {
+ return null
+ }
+
+ for (t in AlarmCategory.values()) {
+ if (t.rawValue == rawValue) {
+ return t
+ }
+ }
+ return null
+ }
+
+ /**
+ * rawValue로 값을 찾기, 못찾으면 defaultValue을 리턴
+ */
+ fun ofRaw(rawValue: Int?, defaultValue: AlarmCategory): AlarmCategory {
+ Preconditions.checkNotNull(defaultValue)
+ if (rawValue == null) {
+ return defaultValue
+ }
+
+ for (t in AlarmCategory.values()) {
+ if (t.rawValue == rawValue) {
+ return t
+ }
+ }
+ return defaultValue
+ }
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/AlarmSource.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/AlarmSource.kt
new file mode 100644
index 0000000000..c433488b7b
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/AlarmSource.kt
@@ -0,0 +1,66 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+
+import com.google.android.gms.common.internal.Preconditions
+import com.google.gson.annotations.SerializedName
+
+enum class AlarmSource private constructor(val rawValue: Int) {
+ @SerializedName("0")
+ NONE(0),
+ @SerializedName("1")
+ PATCH(1),
+ @SerializedName("2")
+ ADM(2),
+ @SerializedName("3")
+ CGM(3),
+ @SerializedName("9")
+ TEST(9);
+
+ val isTest: Boolean
+ get() = this == TEST
+
+ val isCgm: Boolean
+ get() = this == CGM
+
+ companion object {
+
+ /**
+ * rawValue로 값을 찾기, 못찾으면 null을 리턴
+ */
+ @JvmStatic
+ fun ofRaw(rawValue: Int?): AlarmSource {
+ if (rawValue == null) {
+ return NONE
+ }
+
+ for (t in AlarmSource.values()) {
+ if (t.rawValue == rawValue) {
+ return t
+ }
+ }
+ return NONE
+ }
+
+ @JvmStatic
+ fun toRaw(source: AlarmSource?): Int {
+ return if (source != null) source.rawValue else NONE.rawValue
+ }
+
+ /**
+ * rawValue로 값을 찾기, 못찾으면 defaultValue을 리턴
+ */
+ fun ofRaw(rawValue: Int?, defaultValue: AlarmSource): AlarmSource {
+ Preconditions.checkNotNull(defaultValue)
+ if (rawValue == null) {
+ return defaultValue
+ }
+
+ for (t in AlarmSource.values()) {
+ if (t.rawValue == rawValue) {
+ return t
+ }
+ }
+ return defaultValue
+ }
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/BasalStatus.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/BasalStatus.kt
new file mode 100644
index 0000000000..4225e0e523
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/BasalStatus.kt
@@ -0,0 +1,43 @@
+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 isPaused: Boolean
+ get() = this == PAUSED
+
+ val isSuspended: Boolean
+ get() = this == SUSPENDED
+
+ val isStopped: Boolean
+ get() = this == STOPPED
+
+ val isSelected: Boolean
+ get() = this == SELECTED
+
+ val isSelectedGroup: Boolean
+ get() = isStarted || isPaused || isSuspended || isSelected
+
+ 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/BgUnit.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/BgUnit.kt
new file mode 100644
index 0000000000..b1bd632d51
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/BgUnit.kt
@@ -0,0 +1,37 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+enum class BgUnit private constructor(val rawValue: Int, val unitStr: String) {
+ GRAM(1, "mg/dL"),
+ MMOL(2, "mmol/L");
+
+ fun isGram() = GRAM == this
+ fun isMmol() = MMOL == this
+
+ fun getUnit() = unitStr
+
+ companion object {
+ /**
+ * rawValue로 값을 찾기, 못찾으면 null을 리턴
+ */
+ fun ofRaw(rawValue: Int?): BgUnit {
+ for (t in BgUnit.values()) {
+ if (t.rawValue == rawValue) {
+ return t
+ }
+ }
+ return GRAM
+ }
+
+ /**
+ * rawValue로 값을 찾기, 못찾으면 defaultValue을 리턴
+ */
+ fun ofRaw(rawValue: Int, defaultValue: BgUnit): BgUnit {
+ for (t in BgUnit.values()) {
+ if (t.rawValue == rawValue) {
+ return t
+ }
+ }
+ return defaultValue
+ }
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/BolusExDuration.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/BolusExDuration.kt
new file mode 100644
index 0000000000..30c0ae7116
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/BolusExDuration.kt
@@ -0,0 +1,65 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+import android.content.Context
+import androidx.collection.SparseArrayCompat
+import info.nightscout.androidaps.plugins.pump.eopatch.CommonUtils
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+import java.util.*
+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);
+
+ val isOff: Boolean
+ get() = this == OFF
+
+ val isNotOff: Boolean
+ get() = this != OFF
+
+ fun milli(): Long {
+ return TimeUnit.MINUTES.toMillis(this.minute.toLong())
+ }
+
+ companion object {
+ @JvmStatic
+ fun getItemFromIndex(index: Int): BolusExDuration {
+ var reverseIndices = SparseArrayCompat()
+ for (duration in values()) {
+ reverseIndices.put(duration.index, duration)
+ }
+ val result = reverseIndices.get(index)
+ return result ?: OFF
+ }
+
+ @JvmStatic
+ fun ofRaw(rawValue: Int): BolusExDuration {
+ if (rawValue == null) {
+ return OFF
+ }
+
+ for (t in BolusExDuration.values()) {
+ if (t.minute == rawValue) {
+ return t
+ }
+ }
+ return OFF
+ }
+ }
+
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/DeactivationStatus.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/DeactivationStatus.kt
new file mode 100644
index 0000000000..8373e828df
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/DeactivationStatus.kt
@@ -0,0 +1,28 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+
+enum class DeactivationStatus constructor(val rawValue: Int) {
+ DEACTIVATION_FAILED(0),
+ NORMAL_DEACTIVATED(1),
+ FORCE_DEACTIVATED(2);
+
+ val isDeactivated: Boolean
+ get() = this == NORMAL_DEACTIVATED || this == FORCE_DEACTIVATED
+
+ val isNormalSuccess: Boolean
+ get() = this == NORMAL_DEACTIVATED
+
+ val isNormalFailed: Boolean
+ get() = this == DEACTIVATION_FAILED || 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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/Dummy.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/Dummy.kt
new file mode 100644
index 0000000000..f81dff583e
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/Dummy.kt
@@ -0,0 +1,5 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+enum class Dummy {
+ INSTANCE
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/EventType.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/EventType.kt
new file mode 100644
index 0000000000..be02c5fd07
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/EventType.kt
@@ -0,0 +1,25 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+enum class EventType {
+ ACTIVTION_CLICKED,
+ DEACTIVTION_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,
+ SHOW_PROGRESS_DIALOG,
+ DISMISS_PROGRESS_DIALOG,
+ FINISH_ACTIVITY,
+ SHOW_DISCARD_DIALOG,
+ PAUSE_BASAL_FAILED,
+ RESUME_BASAL_FAILED,
+ CALCULATED_BOLUS_CLICKED,
+ EXTENDED_BOLUS_SETTINGS_CLICKED,
+ SAVE_CLICKED
+ ;
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchExpireAlertTime.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchExpireAlertTime.kt
new file mode 100644
index 0000000000..9c3a685294
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchExpireAlertTime.kt
@@ -0,0 +1,52 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+import android.content.Context
+import info.nightscout.androidaps.plugins.pump.eopatch.R
+
+enum class PatchExpireAlertTime private 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);
+
+ companion object {
+
+ fun atIndex(index: Int): PatchExpireAlertTime {
+ for (i in values()) {
+ if (i.index == index) {
+ return i
+ }
+ }
+ return HOUR_1
+ }
+
+ fun atHour(hour: Int): PatchExpireAlertTime {
+ for (i in values()) {
+ if (i.hour == hour) {
+ return i
+ }
+ }
+ return HOUR_1
+ }
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchLifecycle.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchLifecycle.kt
new file mode 100644
index 0000000000..99c8d287c0
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchLifecycle.kt
@@ -0,0 +1,30 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+enum class PatchLifecycle private 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
+
+ companion object {
+ @JvmStatic
+ fun ofRaw(rawValue: Int): PatchLifecycle {
+ for (type in values()) {
+ if (type.rawValue == rawValue) {
+ return type
+ }
+ }
+ return SHUTDOWN
+ }
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchStep.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchStep.kt
new file mode 100644
index 0000000000..eed465a75a
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/PatchStep.kt
@@ -0,0 +1,32 @@
+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 isConnectNew: Boolean
+ get() = this == CONNECT_NEW
+
+ val isSafeDeactivation: Boolean
+ get() = this == SAFE_DEACTIVATION
+
+ val isCheckConnection: Boolean
+ get() = this == CHECK_CONNECTION
+}
\ No newline at end of file
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/SettingKeys.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/SettingKeys.kt
new file mode 100644
index 0000000000..13f6a5474f
--- /dev/null
+++ b/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_RESERVIOR_REMINDERS: Int = R.string.key_eopatch_low_reservior_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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/UnitOrPercent.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/UnitOrPercent.kt
new file mode 100644
index 0000000000..56e27148bb
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/code/UnitOrPercent.kt
@@ -0,0 +1,52 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.code
+
+import com.google.android.gms.common.internal.Preconditions
+
+enum class UnitOrPercent private constructor(val rawValue: Int, val symbol: String) {
+ P(0, "%"),
+ U(1, "U");
+
+
+ fun isPercentage() = this == P
+ fun isU() = this == U
+
+ companion object {
+
+ val U_PER_HOUR = "U/hr"
+ val U_PER_DAY = "U/day"
+
+
+ /**
+ * rawValue로 값을 찾기, 못찾으면 null을 리턴
+ */
+ fun ofRaw(rawValue: Int?): UnitOrPercent? {
+ if (rawValue == null) {
+ return null
+ }
+
+ for (t in UnitOrPercent.values()) {
+ if (t.rawValue == rawValue) {
+ return t
+ }
+ }
+ return null
+ }
+
+ /**
+ * rawValue로 값을 찾기, 못찾으면 defaultValue을 리턴
+ */
+ fun ofRaw(rawValue: Int?, defaultValue: UnitOrPercent): UnitOrPercent {
+ Preconditions.checkNotNull(defaultValue)
+ if (rawValue == null) {
+ return defaultValue
+ }
+
+ for (t in UnitOrPercent.values()) {
+ if (t.rawValue == rawValue) {
+ return t
+ }
+ }
+ return defaultValue
+ }
+ }
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchInjectHelpers.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchInjectHelpers.kt
new file mode 100644
index 0000000000..ca9592d836
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchInjectHelpers.kt
@@ -0,0 +1,17 @@
+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 ActivityScope
+
+@MustBeDocumented
+@Scope
+@Retention(AnnotationRetention.RUNTIME)
+annotation class FragmentScope
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchModule.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchModule.kt
new file mode 100644
index 0000000000..e971078b5d
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchModule.kt
@@ -0,0 +1,140 @@
+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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchPrefModule.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchPrefModule.kt
new file mode 100644
index 0000000000..94b63103d4
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/dagger/EopatchPrefModule.kt
@@ -0,0 +1,44 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.dagger
+
+import android.app.Application
+import android.content.Context
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import info.nightscout.androidaps.plugins.pump.eopatch.ble.*
+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.PatchState
+import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasalManager
+import info.nightscout.shared.sharedPreferences.SP
+import javax.inject.Singleton
+
+@Module
+class EopatchPrefModule {
+ @Provides
+ @Singleton
+ internal fun providePatchConfig(): PatchConfig {
+ return PatchConfig()
+ }
+
+ @Provides
+ @Singleton
+ internal fun provideNormalBasalManager(sp: SP): NormalBasalManager {
+ return NormalBasalManager()
+ }
+
+ @Provides
+ @Singleton
+ internal fun provideTempBasalManager(sp: SP): TempBasalManager {
+ return TempBasalManager()
+ }
+
+ @Provides
+ @Singleton
+ internal fun provideAlarms(): Alarms {
+ return Alarms()
+ }
+}
+
+
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/event/EoPatchEvents.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/event/EoPatchEvents.kt
new file mode 100644
index 0000000000..cd8656fc77
--- /dev/null
+++ b/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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/AppCompatActivityExtension.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/AppCompatActivityExtension.kt
new file mode 100644
index 0000000000..46cb3c2cd5
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/AppCompatActivityExtension.kt
@@ -0,0 +1,62 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.extension
+
+import android.content.Intent
+import android.os.Bundle
+import android.os.Parcelable
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentTransaction
+import java.io.Serializable
+
+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()
+}
+
+fun Intent.fillExtras(params: Array>){
+ fillIntentArguments(this, params)
+}
+
+private fun fillIntentArguments(intent: Intent, params: Array>) {
+ params.forEach {
+ when (val value = it.second) {
+ null -> intent.putExtra(it.first, null as Serializable?)
+ is Int -> intent.putExtra(it.first, value)
+ is Long -> intent.putExtra(it.first, value)
+ is CharSequence -> intent.putExtra(it.first, value)
+ is String -> intent.putExtra(it.first, value)
+ is Float -> intent.putExtra(it.first, value)
+ is Double -> intent.putExtra(it.first, value)
+ is Char -> intent.putExtra(it.first, value)
+ is Short -> intent.putExtra(it.first, value)
+ is Boolean -> intent.putExtra(it.first, value)
+ is Serializable -> intent.putExtra(it.first, value)
+ is Bundle -> intent.putExtra(it.first, value)
+ is Parcelable -> intent.putExtra(it.first, value)
+ is Array<*> -> when {
+ value.isArrayOf() -> intent.putExtra(it.first, value)
+ value.isArrayOf() -> intent.putExtra(it.first, value)
+ value.isArrayOf() -> intent.putExtra(it.first, value)
+ else -> throw Exception("Intent extra ${it.first} has wrong type ${value.javaClass.name}")
+ }
+ is IntArray -> intent.putExtra(it.first, value)
+ is LongArray -> intent.putExtra(it.first, value)
+ is FloatArray -> intent.putExtra(it.first, value)
+ is DoubleArray -> intent.putExtra(it.first, value)
+ is CharArray -> intent.putExtra(it.first, value)
+ is ShortArray -> intent.putExtra(it.first, value)
+ is BooleanArray -> intent.putExtra(it.first, value)
+ else -> throw Exception("Intent extra ${it.first} has wrong type ${value.javaClass.name}")
+ }
+ return@forEach
+ }
+}
\ No newline at end of file
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/BooleanExtension.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/BooleanExtension.kt
new file mode 100644
index 0000000000..1b560c6e0a
--- /dev/null
+++ b/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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/CharSequenceExtesnsion.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/CharSequenceExtesnsion.kt
new file mode 100644
index 0000000000..5763bf815d
--- /dev/null
+++ b/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/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/CompletableExtension.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/CompletableExtension.kt
new file mode 100644
index 0000000000..29c3bbccdb
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/CompletableExtension.kt
@@ -0,0 +1,29 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.extension
+
+import io.reactivex.Completable
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
+import timber.log.Timber
+
+fun Completable.observeOnMainThread(): Completable = observeOn(AndroidSchedulers.mainThread())
+
+fun Completable.observeOnComputation(): Completable = observeOn(Schedulers.computation())
+
+fun Completable.observeOnIo(): Completable = observeOn(Schedulers.io())
+
+fun Completable.subscribeEmpty(): Disposable {
+ return subscribe({}, {})
+}
+
+fun Completable.subscribeEmpty(onComplete: () -> Unit, onError: (Throwable) -> Unit): Disposable {
+ return subscribe(onComplete, onError)
+}
+
+fun Completable.subscribeDefault(): Disposable {
+ return subscribe({ Timber.d("onComplete") }, { Timber.e(it, "onError") })
+}
+
+fun Completable.subscribeDefault(onComplete: () -> Unit): Disposable {
+ return subscribe(onComplete, { Timber.e(it, "onError") })
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/FloatExtension.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/FloatExtension.kt
new file mode 100644
index 0000000000..e8341c7273
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/FloatExtension.kt
@@ -0,0 +1,18 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.extension
+
+fun Float.nearlyEqual(b: Float, epsilon: Float): Boolean {
+ val absA = Math.abs(this)
+ val absB = Math.abs(b)
+ val diff = Math.abs(this - b)
+ return if (this == b) {
+ true
+ } else if (this == 0f || b == 0f || absA + absB < java.lang.Float.MIN_NORMAL) {
+ diff < epsilon * java.lang.Float.MIN_NORMAL
+ } else {
+ diff / Math.min(absA + absB, Float.MAX_VALUE) < epsilon
+ }
+}
+
+fun Float.nearlyNotEqual(b: Float, epsilon: Float): Boolean {
+ return !nearlyEqual(b, epsilon)
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/LongExtension.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/LongExtension.kt
new file mode 100644
index 0000000000..c3de482c8c
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/LongExtension.kt
@@ -0,0 +1,42 @@
+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.getDiffTime(isRelative: Boolean = false): Triple {
+ val inputTimeMillis = this
+ val currentTimeMillis = System.currentTimeMillis()
+ val diffTimeMillis = if (inputTimeMillis > currentTimeMillis) inputTimeMillis - currentTimeMillis else isRelative.takeOne(currentTimeMillis - inputTimeMillis, 0)
+
+ val hours = TimeUnit.MILLISECONDS.toHours(diffTimeMillis)
+ val minutes = TimeUnit.MILLISECONDS.toMinutes(diffTimeMillis - TimeUnit.HOURS.toMillis(hours))
+ val seconds = TimeUnit.MILLISECONDS.toSeconds(diffTimeMillis - TimeUnit.HOURS.toMillis(hours) - TimeUnit.MINUTES.toMillis(minutes))
+
+ return Triple(hours, minutes, seconds)
+}
+
+fun Long.getDiffTime(startTimeMillis: Long): Triple {
+ val inputTimeMillis = this
+ val diffTimeMillis = if (inputTimeMillis > startTimeMillis) inputTimeMillis - startTimeMillis else 0
+
+ val hours = TimeUnit.MILLISECONDS.toHours(diffTimeMillis)
+ val minutes = TimeUnit.MILLISECONDS.toMinutes(diffTimeMillis - TimeUnit.HOURS.toMillis(hours))
+ val seconds = TimeUnit.MILLISECONDS.toSeconds(diffTimeMillis - TimeUnit.HOURS.toMillis(hours) - TimeUnit.MINUTES.toMillis(minutes))
+
+ return Triple(hours, minutes, seconds)
+}
+
+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)
+}
+
+fun Long.getSeconds() : Long {
+ return (this/1000)*1000
+}
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/MaybeExtension.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/MaybeExtension.kt
new file mode 100644
index 0000000000..d0e79e3033
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/MaybeExtension.kt
@@ -0,0 +1,27 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.extension
+
+import io.reactivex.Maybe
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
+import timber.log.Timber
+
+fun Maybe.observeOnMainThread(): Maybe = observeOn(AndroidSchedulers.mainThread())
+
+fun Maybe.observeOnComputation(): Maybe = observeOn(Schedulers.computation())
+
+fun Maybe.observeOnIo(): Maybe = observeOn(Schedulers.io())
+
+fun Maybe.subscribeEmpty(): Disposable = subscribe({}, {}, {})
+
+fun Maybe.subscribeEmpty(onSuccess: (T) -> Unit): Disposable = subscribe(onSuccess, {}, {})
+
+fun Maybe.subscribeEmpty(onSuccess: (T) -> Unit, onError: (Throwable) -> Unit): Disposable = subscribe(onSuccess, onError, {})
+
+fun Maybe.subscribeDefault(): Disposable = subscribe({ Timber.d("onSuccess") }, { Timber.e(it, "onError") }, { Timber.d("onComplete") })
+
+fun Maybe.subscribeDefault(onSuccess: (T) -> Unit): Disposable = subscribe(onSuccess, { Timber.e(it, "onError") }, { Timber.d("onComplete") })
+
+fun Maybe.subscribeDefault(onSuccess: (T) -> Unit, onError: (Throwable) -> Unit): Disposable = subscribe(onSuccess, onError, { Timber.d("onComplete") })
+
+fun Maybe.with(): Maybe = subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
diff --git a/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/ObservableExtension.kt b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/ObservableExtension.kt
new file mode 100644
index 0000000000..d11a4cd368
--- /dev/null
+++ b/eopatch/src/main/java/info/nightscout/androidaps/plugins/pump/eopatch/extension/ObservableExtension.kt
@@ -0,0 +1,27 @@
+package info.nightscout.androidaps.plugins.pump.eopatch.extension
+
+import io.reactivex.Observable
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
+import timber.log.Timber
+
+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