EOPatch2 support

This commit is contained in:
jungsomyeong 2022-02-11 13:58:07 +09:00
parent 479bfc4103
commit cfaf9f228e
170 changed files with 15266 additions and 3 deletions

View file

@ -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')

BIN
app/libs/eopatch_core.aar Normal file

Binary file not shown.

View file

@ -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)

View file

@ -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
]
)

View file

@ -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

View file

@ -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()

View file

@ -10,5 +10,6 @@ enum class ManufacturerType(val description: String) {
Cellnovo("Cellnovo"),
Roche("Roche"),
Ypsomed("Ypsomed"),
G2e("G2e");
G2e("G2e"),
Eoflow("Eoflow");
}

View file

@ -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

View file

@ -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)),

View file

@ -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
}
}

View file

@ -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),

View file

@ -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

View file

@ -0,0 +1,43 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="M0,0h128v128h-128z"
android:strokeAlpha="0"
android:fillColor="#fff"
android:fillAlpha="0"/>
<path
android:pathData="M43.77,113.5C19.18,113.5 4.5,95 4.5,64c0,-41.4 11.62,-49.5 31.69,-49.5H81.77A41.68,41.68 0,0 1,123.5 56V88.17A25.43,25.43 0,0 1,98.05 113.5Z"
android:fillColor="#f7f7f7"/>
<path
android:pathData="M81.77,15A41.18,41.18 0,0 1,123 56V88.17a24.92,24.92 0,0 1,-25 24.83H43.77c-11.64,0 -21.36,-4.36 -28.11,-12.62C8.69,91.85 5,79.27 5,64 5,23 16.43,15 36.19,15H81.77m0,-1H36.19C14.65,14 4,24 4,64c0,32 15.77,50 39.77,50H98.05a25.89,25.89 0,0 0,26 -25.83V56A42.14,42.14 0,0 0,81.77 14Z"
android:fillColor="#424242"/>
<path
android:pathData="M81.59,29.5A26.35,26.35 0,0 1,108 55.72V88.19A10.36,10.36 0,0 1,97.61 98.5h-51c-7.54,0 -13,-1.76 -17.29,-5.53C23.13,87.51 20,77.76 20,64c0,-25.56 1.38,-34.5 16.79,-34.5h44.8m0,-1H36.79C20.58,28.5 19,38.22 19,64c0,31.93 16.49,35.5 27.59,35.5h51A11.35,11.35 0,0 0,109 88.19V55.72A27.32,27.32 0,0 0,81.59 28.5Z"
android:fillColor="#424242"/>
<path
android:pathData="M84.39,57H95.61a0.29,0.29 0,0 0,0.25 -0.26,10.88 10.88,0 0,0 0,-3.48 0.29,0.29 0,0 0,-0.25 -0.26H84.39a0.29,0.29 0,0 0,-0.25 0.26,10.88 10.88,0 0,0 0,3.48A0.29,0.29 0,0 0,84.39 57Z"
android:fillColor="#bdbdbd"/>
<path
android:strokeWidth="1"
android:pathData="M90,55m-9,0a9,9 0,1 1,18 0a9,9 0,1 1,-18 0"
android:fillColor="#00000000"
android:strokeColor="#bdbdbd"/>
<path
android:pathData="M85.5,107.5m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
android:strokeWidth="0.97"
android:fillColor="#fff"
android:strokeColor="#bdbdbd"/>
<path
android:pathData="M10.45,64m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
android:strokeWidth="0.97"
android:fillColor="#fff"
android:strokeColor="#bdbdbd"/>
<path
android:pathData="M85.5,20.5m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
android:strokeWidth="0.97"
android:fillColor="#fff"
android:strokeColor="#bdbdbd"/>
</vector>

View file

@ -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

View file

@ -42,6 +42,7 @@ data class InterfaceIDs(
YPSOPUMP,
MDI,
DIACONN_G8,
EOPATCH2,
USER;
companion object {

View file

@ -168,6 +168,7 @@ data class UserEntry(
Omnipod, //No entry currently
OmnipodEros,
OmnipodDash, //No entry currently
EOPatch2,
MDI,
VirtualPump,
SMS, //From SMS plugin

1
eopatch/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

49
eopatch/build.gradle Normal file
View file

@ -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"
}

Binary file not shown.

21
eopatch/proguard-rules.pro vendored Normal file
View file

@ -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

View file

@ -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)
}
}

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="info.nightscout.androidaps.plugins.pump.eopatch">
<application>
<activity android:name=".ui.EopatchActivity" />
<activity android:name=".ui.AlarmHelperActivity" />
<activity android:name=".ui.DialogHelperActivity" />
<receiver android:name=".OsAlarmReceiver"/>
</application>
</manifest>

View file

@ -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
}
}

View file

@ -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<Number, Int> { it.toInt() }
val TO_FLOAT = Function<Number, Float> { it.toFloat() }
val TO_STRING = Function<Number, String> { it.toString() }
val TO_CLOCK = Function<Number, String>{ 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<Long, Long> {
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 <T : Any> clone(src: T): T {
return GsonHelper.sharedGson().fromJson(GsonHelper.sharedGson().toJson(src), src.javaClass)
}
}

View file

@ -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
}
}

View file

@ -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<Any> = PublishSubject.create()
fun publish(event: Any) {
publishSubject.onNext(event)
}
fun <T> listen(eventType: Class<T>): Observable<T> {
return publishSubject.ofType(eventType)
}
}

View file

@ -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<Boolean> = 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<CustomAction>? {
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
}
}

View file

@ -0,0 +1,46 @@
package info.nightscout.androidaps.plugins.pump.eopatch
import java.util.*
import java.util.function.Function
object FloatFormatters {
val INSULIN = Function<Number, String>{ value -> String.format(Locale.US, CommonUtils.insulinFormat(), value.toFloat()) }
val FAT = Function<Number, String>{ value -> String.format(Locale.US, "%.1f", value.toFloat()) }
val DURATION = Function<Number, String>{ 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!!
}
}
}

View file

@ -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!!
}
}

View file

@ -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<AlarmCode>().apply { add(alarmCode) }))
}
}
}

View file

@ -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);
}
}

View file

@ -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<T> : MaybeObserver<T>, SingleObserver<T>, Observer<T>, FlowableSubscriber<T> {
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())
}
}

View file

@ -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) }
}
}
}

View file

@ -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<AlarmCode, Alarms.AlarmItem>
val registeredAlarm = pm.getAlarms().registered.clone() as HashMap<AlarmCode, Alarms.AlarmItem>
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
}
}

View file

@ -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<Int>
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<Int> {
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<Int>>): Single<Int> {
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<Int> {
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<Int> {
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<Int> {
return Single.fromCallable {
startActivityWithSingleTop(context, createIntentForChangePatch(context))
IAlarmProcess.ALARM_HANDLED
}
}
private fun needleInsertionErrorAction(context: Context): Single<Int> {
return Single.fromCallable {
startActivityWithSingleTop(context, createIntentForCanularInsertionError(context))
IAlarmProcess.ALARM_HANDLED
}
}
private fun inappropriateTemperatureAction(context: Context): Single<Int> {
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()
}
}
}

View file

@ -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<AlarmCode>
fun add(patchAeCodes: Set<PatchAeCode>)
fun remove(alarmKey: AlarmCode): Maybe<AlarmCode>
}
@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<Maybe<*>>()
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<AlarmCode> {
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<AlarmCode>().apply { add(alarmCode) }, isFirst))
return Maybe.just(alarmCode)
}
return registerOsAlarm(alarmCode, triggerTimeMilli)
}
}
override fun add(patchAeCodes: Set<PatchAeCode>) {
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<AlarmCode> {
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<AlarmCode> {
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<Int> {
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)
}
}

View file

@ -0,0 +1,7 @@
package info.nightscout.androidaps.plugins.pump.eopatch.alarm;
public enum AlarmState {
REGISTER,
FIRED,
HANDLE
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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<PatchLifecycle> observePatchLifeCycle();
Observable<PatchState> observePatchState();
BleConnectionState getPatchConnectionState();
void connect();
void disconnect();
PatchState getPatchState();
void updatePatchState(PatchState state);
BolusCurrent getBolusCurrent();
Single<DeactivationStatus> deactivate(long timeout, boolean force);
Observable<BleConnectionState> observePatchConnectionState();
Observable<BolusCurrent> observeBolusCurrent();
void setConnection();
Single<BolusStopResponse> stopNowBolus();
Single<BolusStopResponse> stopExtBolus();
Single<ComboBolusStopResponse> stopComboBolus();
Single<? extends BolusResponse> startQuickBolus(float nowDoseU, float exDoseU,
BolusExDuration exDuration);
Single<? extends BolusResponse> startCalculatorBolus(DetailedBolusInfo detailedBolusInfo);
Single<PatchBooleanResponse> infoReminderSet(boolean infoReminder);
Single<PatchBooleanResponse> setLowReservoir(int doseUnit, int hours);
Single<PatchState> updateConnection();
long getPatchExpiredTime();
Single<BasalScheduleSetResponse> startBasal(NormalBasal basal);
void updatePatchLifeCycle(PatchLifecycleEvent event);
Single<Boolean> startBond(String mac);
Single<Boolean> getPatchInfo(long timeout);
Single<PatchSelfTestResult> selfTest(long timeout);
Observable<Long> startPriming(long timeout, long count);
Single<Boolean> checkNeedleSensing(long timeout);
Single<Boolean> patchActivation(long timeout);
Single<PatchBooleanResponse> stopAeBeep(int aeCode);
Single<TempBasalScheduleSetResponse> startTempBasal(TempBasal tempBasal);
Single<? extends BaseResponse> pauseBasal(float pauseDurationHour);
Single<ScanList> scan(long timeout);
Single<PatchBooleanResponse> stopTempBasal();
Single<TemperatureResponse> getTemperature();
void initBasalSchedule();
void addBolusToHistory(DetailedBolusInfo originalDetailedBolusInfo);
void changeBuzzerSetting();
void changeReminderSetting();
void checkActivationProcess();
}

View file

@ -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<PatchLifecycle> observePatchLifeCycle() {
return pm.observePatchLifeCycle();
}
@Override
public synchronized void updatePatchLifeCycle(PatchLifecycleEvent event) {
pm.updatePatchLifeCycle(event);
}
@Override
public BleConnectionState getPatchConnectionState() {
return patchManager.getPatchConnectionState();
}
@Override
public Observable<BleConnectionState> 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<PatchState> observePatchState() {
return pm.observePatchState();
}
@Override
public long getPatchExpiredTime() {
return pm.getPatchConfig().getPatchExpiredTime();
}
@Override
public BolusCurrent getBolusCurrent() {
return pm.getBolusCurrent();
}
@Override
public Observable<BolusCurrent> 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<Boolean> startBond(String mac) {
return patchManager.startBond(mac);
}
public Single<Boolean> getPatchInfo(long timeout) {
return patchManager.getPatchInfo(timeout);
}
public Single<PatchSelfTestResult> selfTest(long timeout) {
return patchManager.selfTest(timeout);
}
public Single<TemperatureResponse> getTemperature() {
return patchManager.getTemperature();
}
public Observable<Long> startPriming(long timeout, long count) {
return patchManager.startPriming(timeout, count);
}
public Single<Boolean> checkNeedleSensing(long timeout) {
return patchManager.checkNeedleSensing(timeout);
}
public Single<Boolean> patchActivation(long timeout) {
return patchManager.patchActivation(timeout);
}
public Single<BasalScheduleSetResponse> 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<TempBasalScheduleSetResponse> startTempBasal(TempBasal tempBasal) {
return patchManager.startTempBasal(tempBasal);
}
// 템프베이젤 주입 정지
// 템프베이젤이 정지되면 자동으로 노멀베이젤이 활성화된다
// 외부에서 호출된다. 명시적으로 tempBasal 정지. 때는 normalBasal resume PatchState 보고 처리.
public Single<PatchBooleanResponse> 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<BolusStopResponse> stopNowBolus() {
return patchManager.stopNowBolus();
}
public Single<BolusStopResponse> stopExtBolus() {
return patchManager.stopExtBolus();
}
public Single<ComboBolusStopResponse> stopComboBolus(){
return patchManager.stopComboBolus();
}
public Single<DeactivationStatus> deactivate(long timeout, boolean force) {
return patchManager.deactivate(timeout, force);
}
public Single<PatchBooleanResponse> stopBuzz() {
return patchManager.stopBuzz();
}
public Single<PatchBooleanResponse> infoReminderSet(boolean infoReminder) {
return patchManager.infoReminderSet(infoReminder);
}
public Single<PatchBooleanResponse> setLowReservoir(int doseUnit, int hours) {
return patchManager.setLowReservoir(doseUnit, hours);
}
public Single<PatchState> updateConnection() {
return patchManager.updateConnection();
}
public Single<PatchBooleanResponse> stopAeBeep(int aeCode) {
return patchManager.stopAeBeep(aeCode);
}
@Override
public Single<ScanList> 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());
});
}
}
}

View file

@ -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<Intent> 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<PatchConfig> 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<Boolean> startBond(String mac) {
return START_BOND.start(mac);
}
public Single<Boolean> 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<PatchSelfTestResult> selfTest(long timeout) {
return SELF_TEST.start().timeout(timeout, TimeUnit.MILLISECONDS);
}
/**
* Activation Process task #3 PRIMING
* Fragment: fragment_patch_priming
*/
public Single<TemperatureResponse> getTemperature() {
return TEMPERATURE_GET.get()
.timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
}
public Observable<Long> 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<Boolean> 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<Boolean> 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<BasalScheduleSetResponse> 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<BasalStopResponse> 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<TempBasalScheduleSetResponse> startTempBasal(TempBasal tempBasal) {
return startTempBasalTask.start(tempBasal).timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
}
// 템프베이젤 주입 정지
// 템프베이젤이 정지되면 자동으로 노멀베이젤이 활성화된다
// 외부에서 호출된다. 명시적으로 tempBasal 정지. 때는 normalBasal resume PatchState 보고 처리.
@Inject
StopTempBasalTask stopTempBasalTask;
public Single<PatchBooleanResponse> 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<BolusStopResponse> stopNowBolus() {
return stopNowBolusTask.stop().timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
}
public Single<BolusStopResponse> stopExtBolus() {
return stopExtBolusTask.stop().timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
}
public Single<ComboBolusStopResponse> 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<DeactivationStatus> deactivate(long timeout, boolean force) {
return deactivateTask.run(force, timeout);
}
public Single<? extends BaseResponse> stopAll(){
List<Single<? extends BaseResponse>> 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<PatchBooleanResponse> stopBuzz() {
return BUZZER_STOP.stop();
}
@Inject
InfoReminderTask infoReminderTask;
public Single<PatchBooleanResponse> infoReminderSet(boolean infoReminder) {
return infoReminderTask.set(infoReminder).timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
}
@Inject
SetLowReservoirTask setLowReservoirTask;
public Single<PatchBooleanResponse> setLowReservoir(int doseUnit, int hours) {
return setLowReservoirTask.set(doseUnit, hours).timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
}
@Inject
UpdateConnectionTask updateConnectionTask;
public Single<PatchState> updateConnection() {
return updateConnectionTask.update();
}
public Single<PatchBooleanResponse> 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<Boolean> 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<Boolean> 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<KeyPair> 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<byte[]> 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<BleConnectionState> 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;
}
}

View file

@ -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);
}
}
}

View file

@ -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<PatchLifecycle>
fun observePatchConfig(): Observable<PatchConfig>
fun observePatchState(): Observable<PatchState>
fun observeBolusCurrent(): Observable<BolusCurrent>
fun observeAlarm(): Observable<Alarms>
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<PatchLifecycle>
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<PatchLifecycle> {
return observePatchLifeCycle
}
override fun observePatchConfig(): Observable<PatchConfig> {
return mPatchConfig.observe()
}
override fun observePatchState(): Observable<PatchState> {
return mPatchState.observe()
}
override fun observeBolusCurrent(): Observable<BolusCurrent>{
return mBolusCurrent.observe()
}
override fun observeAlarm(): Observable<Alarms> {
return mAlarms.observe()
}
}

View file

@ -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<Boolean> 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());
}
}

View file

@ -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;
}
}

View file

@ -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<DeactivationStatus> 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<TaskFunc> 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();
}
}
}

View file

@ -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<AeCodeResponse> 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();
}
}
}

View file

@ -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<Boolean> get() {
Single<Boolean> 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();
}
}

View file

@ -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<PatchBooleanResponse> 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();
}
}
}

View file

@ -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<Boolean> bolusCheckSubject = BehaviorSubject.create();
private BehaviorSubject<Boolean> exbolusCheckSubject = BehaviorSubject.create();
private BehaviorSubject<Boolean> 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<Boolean> getBolusSebject(){
return bolusCheckSubject.hide();
}
private Observable<Boolean> getExbolusSebject(){
return exbolusCheckSubject.hide();
}
private Observable<Boolean> getBasalSebject(){
return basalCheckSubject.hide();
}
public Single<Long> 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<Long> getInternalSuspendTime() {
return INTERNAL_SUSPEND_TIME_GET.get()
.doOnSuccess(this::checkResponse)
.map(PatchInternalSuspendTimeResponse::getTotalSeconds);
}
private Single<Long> 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<Long> 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<Long> 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);
});
}
}
}

View file

@ -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<Boolean> 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();
}
}
}

View file

@ -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<Boolean> bolusCheckSubject = BehaviorSubject.create();
private BehaviorSubject<Boolean> exbolusCheckSubject = BehaviorSubject.create();
private BehaviorSubject<Boolean> 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<Boolean> getBolusSebject(){
return bolusCheckSubject.hide();
}
private Observable<Boolean> getExbolusSebject(){
return exbolusCheckSubject.hide();
}
private Observable<Boolean> getBasalSebject(){
return basalCheckSubject.hide();
}
public Single<PatchBooleanResponse> 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<Long> getSuspendedTime(long pausedTimestamp, @Nullable AlarmCode alarmCode) {
return Single.just(pausedTimestamp);
}
private Single<Long> 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<Long> 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<Long> 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<PatchBooleanResponse> 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);
});
}
}
}

View file

@ -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<Long> 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<Long> 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 리턴
);
}
}

View file

@ -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<BolusFinishTimeResponse> 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();
}
}
}

View file

@ -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<TempBasalFinishTimeResponse> 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();
}
}
}

View file

@ -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();
}
}

View file

@ -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<PatchSelfTestResult> start() {
Single<PatchSelfTestResult> 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) {
}
}

View file

@ -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<PatchBooleanResponse> 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() {
}
}

View file

@ -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<PatchBooleanResponse> 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();
}
}

View file

@ -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<Boolean> 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);
}
}

View file

@ -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();
}
}

View file

@ -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<BasalScheduleSetResponse> start(NormalBasal basal, boolean resume) {
return isReady().concatMapSingle(v -> startJob(basal, resume)).firstOrError();
}
public Single<BasalScheduleSetResponse> 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();
}
}

View file

@ -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();
}
}

View file

@ -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<TempBasalScheduleSetResponse> 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();
}
}

View file

@ -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<Boolean> bolusCheckSubject = BehaviorSubject.create();
private BehaviorSubject<Boolean> exbolusCheckSubject = BehaviorSubject.create();
private BehaviorSubject<Boolean> basalCheckSubject = BehaviorSubject.create();
@Inject
public StopBasalTask() {
super(TaskFunc.STOP_BASAL);
BASAL_STOP = new BasalStop();
}
private Observable<Boolean> getBolusSebject(){
return bolusCheckSubject.hide();
}
private Observable<Boolean> getExbolusSebject(){
return exbolusCheckSubject.hide();
}
private Observable<Boolean> getBasalSebject(){
return basalCheckSubject.hide();
}
public Single<BasalStopResponse> 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();
}
}

View file

@ -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<ComboBolusStopResponse> stop() {
return isReady()
.concatMapSingle(v -> stopJob())
.firstOrError()
.doOnSuccess(this::checkResponse)
.doOnSuccess(this::onComboBolusStopped)
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, e.getMessage()));
}
public Single<ComboBolusStopResponse> 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();
}
}

View file

@ -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<BolusStopResponse> stop() {
return isReady().concatMapSingle(v -> stopJob()).firstOrError()
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, e.getMessage()));
}
public Single<BolusStopResponse> 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();
}
}

View file

@ -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<BolusStopResponse> stop() {
return isReady()
.observeOn(AndroidSchedulers.mainThread())
.concatMapSingle(v -> stopJob()).firstOrError()
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, e.getMessage()));
}
public Single<BolusStopResponse> 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();
}
}

View file

@ -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<PatchBooleanResponse> stop() {
return isReady().concatMapSingle(v -> stopJob()).firstOrError()
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, e.getMessage()));
}
public Single<PatchBooleanResponse> 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();
}
}

View file

@ -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<Integer> sync(int end) {
return Single.just(1); // 베이젤 싱크 사용 안함
// return isReady()
// .concatMapSingle(v -> syncBoth(pm.getPatchConfig().getLastIndex(), end))
// .firstOrError()
// .doOnSuccess(this::updatePatchLastIndex);
}
public Single<Integer> sync() {
return Single.just(1); // 베이젤 싱크 사용 안함
// return isReady()
// .concatMapSingle(v -> getLastIndex())
// .concatMapSingle(end -> syncBoth(pm.getPatchConfig().getLastIndex(), end))
// .firstOrError()
// .doOnSuccess(this::updatePatchLastIndex);
}
private Single<Integer> getLastIndex() {
return BASAL_HISTORY_INDEX_GET.get()
.doOnSuccess(this::checkResponse)
.map(BasalHistoryIndexResponse::getLastFinishedIndex);
}
private Single<Integer> 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();
}
}
}

View file

@ -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<TaskFunc, TaskBase> 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<TaskFunc> isReady() {
return taskQueue.isReady(func).doOnNext(v -> preCondition());
}
protected Observable<TaskFunc> 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();
}
}
}

View file

@ -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
}

View file

@ -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<PatchTask> queue = new LinkedList<>();
private int sequence = 0;
private final BehaviorSubject<PatchTask> ticketSubject = BehaviorSubject.create();
private final BehaviorSubject<Integer> sizeSubject = BehaviorSubject.createDefault(0);
@Inject
public TaskQueue() {
}
protected Observable<Integer> observeQueue() {
return sizeSubject.distinctUntilChanged();
}
protected synchronized Observable<TaskFunc> 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<TaskFunc> 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<PatchTask> 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;
}
}
}

View file

@ -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<PatchState> update() {
return isReady().concatMapSingle(v -> updateJob()).firstOrError();
}
public Single<PatchState> 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();
}
}
}

View file

@ -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
}
}
}

View file

@ -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
}
}
}

View file

@ -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
}
}
}

View file

@ -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
}
}
}

View file

@ -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<BolusExDuration>()
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
}
}
}

View file

@ -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
}
}
}
}

View file

@ -0,0 +1,5 @@
package info.nightscout.androidaps.plugins.pump.eopatch.code
enum class Dummy {
INSTANCE
}

View file

@ -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
;
}

View file

@ -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
}
}
}

View file

@ -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
}
}
}

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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
}
}
}

View file

@ -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

View file

@ -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<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>): 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
}

View file

@ -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()
}
}

View file

@ -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<AlarmCode>, 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()

View file

@ -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<out Pair<String, Any?>>){
fillIntentArguments(this, params)
}
private fun fillIntentArguments(intent: Intent, params: Array<out Pair<String, Any?>>) {
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<CharSequence>() -> intent.putExtra(it.first, value)
value.isArrayOf<String>() -> intent.putExtra(it.first, value)
value.isArrayOf<Parcelable>() -> 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
}
}

View file

@ -0,0 +1,5 @@
package info.nightscout.androidaps.plugins.pump.eopatch.extension
fun <T> Boolean.takeOne(whenTrue: T, whenFalse: T): T {
return if(this) whenTrue else whenFalse
}

View file

@ -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
}

View file

@ -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") })
}

View file

@ -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)
}

View file

@ -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<Long, Long, Long> {
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<Long, Long, Long> {
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
}

View file

@ -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 <T> Maybe<T>.observeOnMainThread(): Maybe<T> = observeOn(AndroidSchedulers.mainThread())
fun <T> Maybe<T>.observeOnComputation(): Maybe<T> = observeOn(Schedulers.computation())
fun <T> Maybe<T>.observeOnIo(): Maybe<T> = observeOn(Schedulers.io())
fun <T> Maybe<T>.subscribeEmpty(): Disposable = subscribe({}, {}, {})
fun <T> Maybe<T>.subscribeEmpty(onSuccess: (T) -> Unit): Disposable = subscribe(onSuccess, {}, {})
fun <T> Maybe<T>.subscribeEmpty(onSuccess: (T) -> Unit, onError: (Throwable) -> Unit): Disposable = subscribe(onSuccess, onError, {})
fun <T> Maybe<T>.subscribeDefault(): Disposable = subscribe({ Timber.d("onSuccess") }, { Timber.e(it, "onError") }, { Timber.d("onComplete") })
fun <T> Maybe<T>.subscribeDefault(onSuccess: (T) -> Unit): Disposable = subscribe(onSuccess, { Timber.e(it, "onError") }, { Timber.d("onComplete") })
fun <T> Maybe<T>.subscribeDefault(onSuccess: (T) -> Unit, onError: (Throwable) -> Unit): Disposable = subscribe(onSuccess, onError, { Timber.d("onComplete") })
fun <T> Maybe<T>.with(): Maybe<T> = subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())

View file

@ -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 <T> Observable<T>.observeOnMainThread(): Observable<T> = observeOn(AndroidSchedulers.mainThread())
fun <T> Observable<T>.observeOnComputation(): Observable<T> = observeOn(Schedulers.computation())
fun <T> Observable<T>.observeOnIo(): Observable<T> = observeOn(Schedulers.io())
fun <T> Observable<T>.subscribeEmpty(): Disposable = subscribe({}, {}, {})
fun <T> Observable<T>.subscribeEmpty(onSuccess: (T) -> Unit): Disposable = subscribe(onSuccess, {}, {})
fun <T> Observable<T>.subscribeEmpty(onSuccess: (T) -> Unit, onError: (Throwable) -> Unit): Disposable = subscribe(onSuccess, onError, {})
fun <T> Observable<T>.subscribeDefault(): Disposable = subscribe({ Timber.d("onSuccess") }, { Timber.e(it, "onError") }, { Timber.d("onComplete") })
fun <T> Observable<T>.subscribeDefault(onSuccess: (T) -> Unit): Disposable = subscribe(onSuccess, { Timber.e(it, "onError") }, { Timber.d("onComplete") })
fun <T> Observable<T>.subscribeDefault(onSuccess: (T) -> Unit, onError: (Throwable) -> Unit): Disposable = subscribe(onSuccess, onError, { Timber.d("onComplete") })
fun <T> Observable<T>.with(): Observable<T> = subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())

Some files were not shown because too many files have changed in this diff Show more