Merge pull request #2161 from nightscout/eopatch2

EOpatch2
This commit is contained in:
Milos Kozak 2022-11-03 22:44:11 +01:00 committed by GitHub
commit da77dec5c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
174 changed files with 13649 additions and 16 deletions

View file

@ -159,6 +159,10 @@ android {
}
useLibrary "org.apache.http.legacy"
dataBinding { //Deleting it causes a binding error
enabled = true
}
}
allprojects {
@ -185,6 +189,7 @@ dependencies {
implementation project(':pump:danars')
implementation project(':pump:danar')
implementation project(':pump:diaconn')
implementation project(':pump:eopatch')
implementation project(':insight')
implementation project(':pump:medtronic')
implementation project(':pump:pump-common')

View file

@ -38,6 +38,7 @@ import info.nightscout.androidaps.plugins.general.wear.WearPlugin
import info.nightscout.androidaps.plugins.general.xdripStatusline.StatusLinePlugin
import info.nightscout.plugins.insulin.InsulinOrefFreePeakPlugin
import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin
import info.nightscout.androidaps.plugins.pump.eopatch.EopatchPumpPlugin
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
@ -99,6 +100,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
@Inject lateinit var virtualPumpPlugin: VirtualPumpPlugin
@Inject lateinit var wearPlugin: WearPlugin
@Inject lateinit var maintenancePlugin: MaintenancePlugin
@Inject lateinit var eopatchPumpPlugin: EopatchPumpPlugin
@Inject lateinit var passwordCheck: PasswordCheck
@Inject lateinit var nsSettingStatus: NSSettingsStatus
@ -187,6 +189,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
addPreferencesFromResourceIfEnabled(comboPlugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(medtronicPumpPlugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(diaconnG8Plugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(eopatchPumpPlugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResource(R.xml.pref_pump, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(virtualPumpPlugin, rootKey)
addPreferencesFromResourceIfEnabled(insulinOrefFreePeakPlugin, rootKey)

View file

@ -19,6 +19,7 @@ import info.nightscout.androidaps.insight.di.InsightModule
import info.nightscout.androidaps.plugin.general.openhumans.di.OpenHumansModule
import info.nightscout.androidaps.plugins.pump.common.di.PumpCommonModule
import info.nightscout.androidaps.plugins.pump.common.di.RileyLinkModule
import info.nightscout.androidaps.plugins.pump.eopatch.dagger.EopatchModule
import info.nightscout.androidaps.plugins.pump.medtronic.di.MedtronicModule
import info.nightscout.androidaps.plugins.pump.omnipod.dash.di.OmnipodDashModule
import info.nightscout.androidaps.plugins.pump.omnipod.eros.di.OmnipodErosModule
@ -69,6 +70,7 @@ import javax.inject.Singleton
WorkersModule::class,
DiaconnG8Module::class,
OpenHumansModule::class,
EopatchModule::class,
SharedModule::class,
UiModule::class,
InsulinModule::class

View file

@ -44,6 +44,7 @@ import info.nightscout.plugins.insulin.InsulinOrefUltraRapidActingPlugin
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin
import info.nightscout.androidaps.plugins.pump.eopatch.EopatchPumpPlugin
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin
import info.nightscout.androidaps.plugins.pump.omnipod.dash.OmnipodDashPumpPlugin
@ -182,9 +183,15 @@ abstract class PluginsModule {
@Binds
@PumpDriver
@IntoMap
@IntKey(160)
@IntKey(155)
abstract fun bindDiaconnG8Plugin(plugin: DiaconnG8Plugin): PluginBase
@Binds
@PumpDriver
@IntoMap
@IntKey(156)
abstract fun bindEopatchPumpPlugin(plugin: EopatchPumpPlugin): PluginBase
@Binds
@AllConfigs
@IntoMap

View file

@ -16,6 +16,7 @@ import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.WarnColors
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.plugins.pump.eopatch.AppConstant
import info.nightscout.shared.sharedPreferences.SP
import javax.inject.Inject
import javax.inject.Singleton
@ -34,7 +35,15 @@ class StatusLightHandler @Inject constructor(
/**
* applies the extended statusLight subview on the overview fragment
*/
fun updateStatusLights(careportal_cannula_age: TextView?, careportal_insulin_age: TextView?, careportal_reservoir_level: TextView?, careportal_sensor_age: TextView?, careportal_sensor_battery_level: TextView?, careportal_pb_age: TextView?, careportal_battery_level: TextView?) {
fun updateStatusLights(
careportal_cannula_age: TextView?,
careportal_insulin_age: TextView?,
careportal_reservoir_level: TextView?,
careportal_sensor_age: TextView?,
careportal_sensor_battery_level: TextView?,
careportal_pb_age: TextView?,
careportal_battery_level: TextView?
) {
val pump = activePlugin.activePump
val bgSource = activePlugin.activeBgSource
handleAge(careportal_cannula_age, TherapyEvent.Type.CANNULA_CHANGE, R.string.key_statuslights_cage_warning, 48.0, R.string.key_statuslights_cage_critical, 72.0)
@ -46,7 +55,11 @@ class StatusLightHandler @Inject constructor(
val insulinUnit = rh.gs(R.string.insulin_unit_shortname)
if (pump.model() == PumpType.OMNIPOD_EROS || pump.model() == PumpType.OMNIPOD_DASH) {
handleOmnipodReservoirLevel(careportal_reservoir_level, R.string.key_statuslights_res_critical, 10.0, R.string.key_statuslights_res_warning, 80.0, pump.reservoirLevel, insulinUnit)
handlePatchReservoirLevel(careportal_reservoir_level, R.string.key_statuslights_res_critical, 10.0, R.string.key_statuslights_res_warning, 80.0, pump.reservoirLevel, insulinUnit,
OmnipodConstants.MAX_RESERVOIR_READING)
} else if (pump.model() == PumpType.EOFLOW_EOPATCH2) {
handlePatchReservoirLevel(careportal_reservoir_level, R.string.key_statuslights_res_critical, 10.0, R.string.key_statuslights_res_warning, 80.0, pump.reservoirLevel, insulinUnit,
AppConstant.MAX_RESERVOIR_READING)
} else {
handleLevel(careportal_reservoir_level, R.string.key_statuslights_res_critical, 10.0, R.string.key_statuslights_res_warning, 80.0, pump.reservoirLevel, insulinUnit)
}
@ -95,14 +108,16 @@ class StatusLightHandler @Inject constructor(
// Omnipod only reports reservoir level when it's 50 units or less, so we display "50+U" for any value > 50
@Suppress("SameParameterValue")
private fun handleOmnipodReservoirLevel(view: TextView?, criticalSetting: Int, criticalDefaultValue: Double, warnSetting: Int, warnDefaultValue: Double, level: Double, units: String) {
if (level >= OmnipodConstants.MAX_RESERVOIR_READING) {
private fun handlePatchReservoirLevel(
view: TextView?, criticalSetting: Int, criticalDefaultValue: Double, warnSetting: Int,
warnDefaultValue: Double, level: Double, units: String, maxReading: Double
) {
if (level >= maxReading) {
@Suppress("SetTextI18n")
view?.text = " 50+$units"
view?.text = " ${maxReading.toInt()}+$units"
view?.setTextColor(rh.gac(view.context, R.attr.defaultTextColor))
} else {
handleLevel(view, criticalSetting, criticalDefaultValue, warnSetting, warnDefaultValue, level, units)
}
}
}

View file

@ -108,6 +108,7 @@
<item>DanaRv2</item>
<item>DanaI</item>
<item>Diaconn G8</item>
<item>Eoflow Eopatch2</item>
<item>Medtronic 512/712</item>
<item>Medtronic 515/715</item>
<item>Medtronic 522/722</item>

View file

@ -38,6 +38,9 @@ buildscript {
androidx_junit_version = '1.1.3'
androidx_rules_version = '1.4.0'
rxandroidble_version = '1.12.1'
replayshare_version = '2.2.0'
wearable_version = '2.9.0'
play_services_wearable_version = '17.1.0'
play_services_location_version = '20.0.0'

View file

@ -98,6 +98,7 @@ sealed class ProfileSealed(
override fun isValid(from: String, pump: Pump, config: Config, rh: ResourceHelper, rxBus: RxBus, hardLimits: HardLimits, sendNotifications: Boolean): Profile.ValidityCheck {
val validityCheck = Profile.ValidityCheck()
val description = pump.pumpDescription
for (basal in basalBlocks) {
val basalAmount = basal.amount * percentage / 100.0
if (!description.is30minBasalRatesCapable) {
@ -142,6 +143,7 @@ sealed class ProfileSealed(
break
}
}
if (!hardLimits.isInRange(dia, hardLimits.minDia(), hardLimits.maxDia())) {
validityCheck.isValid = false
validityCheck.reasons.add(rh.gs(R.string.value_out_of_hard_limits, rh.gs(R.string.profile_dia), dia))

View file

@ -17,7 +17,7 @@ interface CommandQueue {
fun independentConnect(reason: String, callback: Callback?)
fun bolusInQueue(): Boolean
fun bolus(detailedBolusInfo: DetailedBolusInfo, callback: Callback?): Boolean
fun cancelAllBoluses(id: Long)
fun cancelAllBoluses(id: Long?)
fun stopPump(callback: Callback?)
fun startPump(callback: Callback?)
fun setTBROverNotification(callback: Callback?, enable: Boolean)

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

@ -25,6 +25,7 @@ enum class PumpCapability {
OmnipodCapabilities(arrayOf(Bolus, TempBasal, BasalProfileSet, BasalRate30min)),
YpsomedCapabilities(arrayOf(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, ReplaceBattery, TDD, ManualTDDLoad)), // BasalRates (separately grouped)
DiaconnCapabilities(arrayOf(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, ReplaceBattery, TDD, ManualTDDLoad)), //
EopatchCapabilities(arrayOf(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, BasalRate30min)),
BasalRate_Duration15minAllowed,
BasalRate_Duration30minAllowed,
BasalRate_Duration15and30minAllowed(arrayOf(BasalRate_Duration15minAllowed, BasalRate_Duration30minAllowed)),

View file

@ -370,9 +370,25 @@ enum class PumpType {
baseBasalStep = 0.01,
baseBasalSpecialSteps = null,
pumpCapability = PumpCapability.DiaconnCapabilities,
source = Sources.DiaconnG8,
useHardwareLink = true
);
source = Sources.DiaconnG8),
//EOPatch Pump
EOFLOW_EOPATCH2(description = "Eoflow Eopatch2",
manufacturer = ManufacturerType.Eoflow,
model = "Eopatch",
bolusSize = 0.05,
specialBolusSize = null,
extendedBolusSettings = DoseSettings(0.05, 30, 8 * 60, 0.05, 25.0),
pumpTempBasalType = PumpTempBasalType.Absolute,
tbrSettings = DoseSettings(0.05, 30, 12 * 60, 0.0, 15.0),
specialBasalDurations = PumpCapability.BasalRate_Duration30minAllowed,
baseBasalMinValue = 0.05,
baseBasalMaxValue = 15.0,
baseBasalStep = 0.05,
baseBasalSpecialSteps = null,
pumpCapability = PumpCapability.EopatchCapabilities,
isPatchPump = true,
source = Sources.EOPatch2);
val description: String
var manufacturer: ManufacturerType? = null
@ -460,7 +476,8 @@ enum class PumpType {
InterfaceIDs.PumpType.MDI -> MDI
InterfaceIDs.PumpType.USER -> USER
InterfaceIDs.PumpType.DIACONN_G8 -> DIACONN_G8
InterfaceIDs.PumpType.CACHE -> TODO()
InterfaceIDs.PumpType.EOPATCH2 -> EOFLOW_EOPATCH2
InterfaceIDs.PumpType.CACHE -> CACHE
}
}
@ -591,6 +608,7 @@ enum class PumpType {
MDI -> InterfaceIDs.PumpType.MDI
USER -> InterfaceIDs.PumpType.USER
DIACONN_G8 -> InterfaceIDs.PumpType.DIACONN_G8
EOFLOW_EOPATCH2 -> InterfaceIDs.PumpType.EOPATCH2
CACHE -> InterfaceIDs.PumpType.CACHE
}
}

View file

@ -139,6 +139,7 @@ class UserEntryMapper {
Omnipod (UserEntry.Sources.Omnipod),
OmnipodEros (UserEntry.Sources.OmnipodEros),
OmnipodDash (UserEntry.Sources.OmnipodDash),
EOPatch2 (UserEntry.Sources.EOPatch2),
MDI (UserEntry.Sources.MDI),
VirtualPump (UserEntry.Sources.VirtualPump),
SMS (UserEntry.Sources.SMS),

View file

@ -95,6 +95,7 @@ class UserEntryPresentationHelper @Inject constructor(
Sources.Omnipod -> R.drawable.ic_patch_pump_outline
Sources.OmnipodEros -> R.drawable.ic_patch_pump_outline
Sources.OmnipodDash -> R.drawable.ic_patch_pump_outline
Sources.EOPatch2 -> R.drawable.ic_eopatch2_128
Sources.MDI -> R.drawable.ic_ict
Sources.VirtualPump -> R.drawable.ic_virtual_pump
Sources.SMS -> R.drawable.ic_sms

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

@ -43,6 +43,8 @@ files:
translation: /insight/src/main/res/values-%android_code%/exceptions.xml
- source: /automation/src/main/res/values/strings.xml
translation: /automation/src/main/res/values-%android_code%/strings.xml
- source: /pump/eopatch/src/main/res/values/strings.xml
translation: /pump/eopatch/src/main/res/values-%android_code%/strings.xml
- source: /pump/diaconn/src/main/res/values/strings.xml
translation: /pump/diaconn/src/main/res/values-%android_code%/strings.xml
- source: /pump/pump-common/src/main/res/values/strings.xml

View file

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

View file

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

View file

@ -17,7 +17,7 @@
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
org.gradle.parallel=true
org.gradle.warning.mode=all
org.gradle.jvmargs=-Xmx2g -XX:+UseParallelGC
org.gradle.jvmargs=-Xmx3g -XX:+UseParallelGC
android.enableJetifier=false
android.useAndroidX=true

View file

@ -348,7 +348,7 @@ class CommandQueueImplementation @Inject constructor(
}
@Synchronized
override fun cancelAllBoluses(id: Long) {
override fun cancelAllBoluses(id: Long?) {
if (!isRunning(CommandType.BOLUS)) {
rxBus.send(EventDismissBolusProgressIfRunning(PumpEnactResult(injector).success(true).enacted(false), id))
}

View file

@ -41,7 +41,8 @@ project.afterEvaluate {
'**/R2$*.class',
'**/*Directions$*',
'**/*Directions.*',
'**/*Binding.*'
'**/*Binding.*',
'**/BR.class'
]
def jClasses = subprojects.collect { proj ->

1
pump/eopatch-core/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,2 @@
configurations.create("default")
artifacts.add("default", file('libs/eopatch_core.aar'))

View file

Binary file not shown.

21
pump/eopatch-core/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,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

1
pump/eopatch/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

30
pump/eopatch/build.gradle Normal file
View file

@ -0,0 +1,30 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-allopen'
apply plugin: 'com.hiya.jacoco-android'
apply from: "${project.rootDir}/core/android_dependencies.gradle"
apply from: "${project.rootDir}/core/android_module_dependencies.gradle"
apply from: "${project.rootDir}/core/test_dependencies.gradle"
apply from: "${project.rootDir}/core/jacoco_global.gradle"
android {
namespace 'info.nightscout.androidaps.plugins.pump.eopatch'
dataBinding {
enabled = true
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':pump:eopatch-core')
implementation project(':libraries')
implementation project(':shared')
implementation project(':database')
implementation project(':core')
//RxAndroidBle
implementation "com.polidea.rxandroidble3:rxandroidble:1.16.0"
implementation "com.jakewharton.rx3:replaying-share:3.0.0"
}

21
pump/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,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<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,30 @@
package info.nightscout.androidaps.plugins.pump.eopatch
interface AppConstant {
companion object {
const val BASAL_MIN_AMOUNT = 0.05f
const val INSULIN_UNIT_P = 0.05f
const val INSULIN_UNIT_STEP_U = INSULIN_UNIT_P
const val OFF = 0
const val ON = 1
const val PUMP_DURATION_MILLI = 4 * 1000L
const val BASAL_RATE_PER_HOUR_MIN = BASAL_MIN_AMOUNT
const val SEGMENT_MAX_SIZE_48 = 48
const val SEGMENT_COUNT_MAX = SEGMENT_MAX_SIZE_48
const val BOLUS_ACTIVE_EXTENDED_WAIT = 0x2
const val BOLUS_UNIT_STEP = INSULIN_UNIT_STEP_U
const val DAY_START_MINUTE = 0 * 60
const val DAY_END_MINUTE = 24 * 60
const val INSULIN_DURATION_MIN = 2.0f
const val MAX_RESERVOIR_READING = 50.0
}
}

View file

@ -0,0 +1,93 @@
package info.nightscout.androidaps.plugins.pump.eopatch
import io.reactivex.rxjava3.disposables.Disposable
import java.util.*
import kotlin.math.abs
import kotlin.math.min
object CommonUtils {
fun dispose(vararg disposable: Disposable?) {
for (d in disposable){
d?.let {
if (!it.isDisposed) {
it.dispose()
}
}
}
}
fun hasText(str: CharSequence?): Boolean {
if (str == null || str.isEmpty()) {
return false
}
val strLen = str.length
for (i in 0 until strLen) {
if (!Character.isWhitespace(str[i])) {
return true
}
}
return false
}
fun hasText(str: String?): Boolean {
return str?.let{hasText(it as CharSequence)}?:false
}
fun isStringEmpty(cs: CharSequence?): Boolean {
return cs == null || cs.isEmpty()
}
@JvmStatic fun dateString(millis: Long): String {
if(millis == 0L) return ""
val c = Calendar.getInstance()
c.timeInMillis = millis
return dateString(c)
}
fun dateString(c: Calendar): String {
return String.format(Locale.US, "%04d-%02d-%02d %02d:%02d:%02d",
c.get(Calendar.YEAR),
c.get(Calendar.MONTH) + 1,
c.get(Calendar.DAY_OF_MONTH),
c.get(Calendar.HOUR_OF_DAY),
c.get(Calendar.MINUTE),
c.get(Calendar.SECOND))
}
fun getRemainHourMin(timeMillis: Long): Pair<Long, Long> {
val diffHours: Long
var diffMinutes: Long
if (timeMillis >= 0) {
diffMinutes = abs(timeMillis / (60 * 1000) % 60) + 1
if (diffMinutes == 60L) {
diffMinutes = 0
diffHours = abs(timeMillis / (60 * 60 * 1000)) + 1
} else {
diffHours = abs(timeMillis / (60 * 60 * 1000))
}
} else {
diffMinutes = abs(timeMillis / (60 * 1000) % 60)
diffHours = abs(timeMillis / (60 * 60 * 1000))
}
return Pair(diffHours, diffMinutes)
}
fun nearlyEqual(a: Float, b: Float, epsilon: Float): Boolean {
val absA = abs(a)
val absB = abs(b)
val diff = abs(a - b)
return if (a == b) {
true
} else if (a == 0f || b == 0f || absA + absB < java.lang.Float.MIN_NORMAL) {
diff < epsilon * java.lang.Float.MIN_NORMAL
} else {
diff / min(absA + absB, Float.MAX_VALUE) < epsilon
}
}
fun <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.rxjava3.core.Observable
import io.reactivex.rxjava3.subjects.PublishSubject
object EoPatchRxBus {
private val publishSubject: PublishSubject<Any> = PublishSubject.create()
fun publish(event: Any) {
publishSubject.onNext(event)
}
fun <T: Any> listen(eventType: Class<T>): Observable<T> {
return publishSubject.ofType(eventType)
}
}

View file

@ -0,0 +1,562 @@
package info.nightscout.androidaps.plugins.pump.eopatch
import android.os.SystemClock
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.data.DetailedBolusInfo
import info.nightscout.androidaps.data.PumpEnactResult
import info.nightscout.androidaps.events.EventAppInitialized
import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.interfaces.CommandQueue
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.Pump
import info.nightscout.androidaps.interfaces.PumpDescription
import info.nightscout.androidaps.interfaces.PumpPluginBase
import info.nightscout.androidaps.interfaces.PumpSync
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.common.ManufacturerType
import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction
import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmManager
import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPatchManager
import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager
import info.nightscout.androidaps.plugins.pump.eopatch.code.BolusExDuration
import info.nightscout.androidaps.plugins.pump.eopatch.code.SettingKeys
import info.nightscout.androidaps.plugins.pump.eopatch.ui.EopatchOverviewFragment
import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasal
import info.nightscout.androidaps.queue.commands.CustomCommand
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.TimeChangeType
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.functions.Consumer
import io.reactivex.rxjava3.subjects.BehaviorSubject
import org.json.JSONObject
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.min
import kotlin.math.roundToInt
@Singleton
class EopatchPumpPlugin @Inject constructor(
injector: HasAndroidInjector,
aapsLogger: AAPSLogger,
rh: ResourceHelper,
commandQueue: CommandQueue,
private val aapsSchedulers: AapsSchedulers,
private val rxBus: RxBus,
private val fabricPrivacy: FabricPrivacy,
private val dateUtil: DateUtil,
private val pumpSync: PumpSync,
private val patchManager: IPatchManager,
private val alarmManager: IAlarmManager,
private val preferenceManager: IPreferenceManager
):PumpPluginBase(PluginDescription()
.mainType(PluginType.PUMP)
.fragmentClass(EopatchOverviewFragment::class.java.name)
.pluginIcon(R.drawable.ic_eopatch2_128)
.pluginName(R.string.eopatch)
.shortName(R.string.eopatch_shortname)
.preferencesId(R.xml.pref_eopatch)
.description(R.string.eopatch_pump_description), injector, aapsLogger, rh, commandQueue
), Pump {
private val mDisposables = CompositeDisposable()
private var mPumpType: PumpType = PumpType.EOFLOW_EOPATCH2
private var mLastDataTime: Long = 0
private val mPumpDescription = PumpDescription(mPumpType)
override fun onStart() {
super.onStart()
mDisposables.add(rxBus
.toObservable(EventPreferenceChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ event: EventPreferenceChange ->
if (event.isChanged(rh, SettingKeys.LOW_RESERVOIR_REMINDERS) || event.isChanged(rh, SettingKeys.EXPIRATION_REMINDERS)) {
patchManager.changeReminderSetting()
} else if (event.isChanged(rh, SettingKeys.BUZZER_REMINDERS)) {
patchManager.changeBuzzerSetting()
}
}) { throwable: Throwable -> fabricPrivacy.logException(throwable) }
)
mDisposables.add(rxBus
.toObservable(EventAppInitialized::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
preferenceManager.init()
patchManager.init()
alarmManager.init()
}) { throwable: Throwable -> fabricPrivacy.logException(throwable) }
)
}
override fun specialEnableCondition(): Boolean {
//BG -> FG, restart patch activation and trigger unhandled alarm
if(preferenceManager.isInitDone()) {
patchManager.checkActivationProcess()
alarmManager.restartAll()
}
return super.specialEnableCondition()
}
override fun onStop() {
super.onStop()
aapsLogger.debug(LTag.PUMP, "EOPatchPumpPlugin onStop()")
}
override fun isInitialized(): Boolean {
return isConnected() && patchManager.isActivated
}
override fun isSuspended(): Boolean {
return patchManager.patchState.isNormalBasalPaused
}
override fun isBusy(): Boolean {
return false
}
override fun isConnected(): Boolean {
return if(patchManager.isDeactivated) true else patchManager.patchConnectionState.isConnected
}
override fun isConnecting(): Boolean {
return patchManager.patchConnectionState.isConnecting
}
override fun isHandshakeInProgress(): Boolean {
return false
}
override fun finishHandshaking() {
}
override fun connect(reason: String) {
aapsLogger.debug(LTag.PUMP,"EOPatch connect - reason:$reason")
mLastDataTime = System.currentTimeMillis()
}
override fun disconnect(reason: String) {
aapsLogger.debug(LTag.PUMP,"EOPatch disconnect - reason:$reason")
}
override fun stopConnecting() {
}
override fun getPumpStatus(reason: String) {
if (patchManager.isActivated) {
if ("SMS" == reason) {
aapsLogger.debug("Acknowledged AAPS getPumpStatus request it was requested through an SMS")
}else{
aapsLogger.debug("Acknowledged AAPS getPumpStatus request")
}
mDisposables.add(patchManager.updateConnection()
.subscribe(Consumer {
mLastDataTime = System.currentTimeMillis()
})
)
}
}
override fun setNewBasalProfile(profile: Profile): PumpEnactResult {
mLastDataTime = System.currentTimeMillis()
if(patchManager.isActivated){
if(patchManager.patchState.isTempBasalActive || patchManager.patchState.isBolusActive){
return PumpEnactResult(injector)
}else{
var isSuccess: Boolean? = null
val result: BehaviorSubject<Boolean> = BehaviorSubject.create()
val disposable = result.hide()
.subscribe {
isSuccess = it
}
val nb = preferenceManager.getNormalBasalManager().convertProfileToNormalBasal(profile)
mDisposables.add(patchManager.startBasal(nb)
.observeOn(aapsSchedulers.main)
.subscribe({ response ->
result.onNext(response.isSuccess)
}, {
result.onNext(false)
})
)
do{
SystemClock.sleep(100)
}while(isSuccess == null)
disposable.dispose()
aapsLogger.info(LTag.PUMP, "Basal Profile was set: ${isSuccess?:false}")
if(isSuccess == true) {
rxBus.send(EventNewNotification(Notification(Notification.PROFILE_SET_OK, rh.gs(R.string.profile_set_ok), Notification.INFO, 60)))
return PumpEnactResult(injector).success(true).enacted(true)
}else{
return PumpEnactResult(injector)
}
}
}else{
preferenceManager.getNormalBasalManager().setNormalBasal(profile)
preferenceManager.flushNormalBasalManager()
rxBus.send(EventNewNotification(Notification(Notification.PROFILE_SET_OK, rh.gs(R.string.profile_set_ok), Notification.INFO, 60)))
return PumpEnactResult(injector).success(true).enacted(true)
}
}
override fun isThisProfileSet(profile: Profile): Boolean {
// if (!patchManager.isActivated) {
// return true
// }
val ret = preferenceManager.getNormalBasalManager().isEqual(profile)
aapsLogger.info(LTag.PUMP, "Is this profile set? $ret")
return ret
}
override fun lastDataTime(): Long {
return mLastDataTime
}
override val baseBasalRate: Double
get() {
if (!patchManager.isActivated || patchManager.patchState.isNormalBasalPaused) {
return 0.0
}
return preferenceManager.getNormalBasalManager().normalBasal.getCurrentSegment()?.doseUnitPerHour?.toDouble()?:0.05
}
override val reservoirLevel: Double
get() {
if (!patchManager.isActivated) {
return 0.0
}
return patchManager.patchState.remainedInsulin.toDouble()
}
override val batteryLevel: Int
get() {
return if(patchManager.isActivated) {
patchManager.patchState.batteryLevel()
}else{
0
}
}
override fun deliverTreatment(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult {
if (detailedBolusInfo.insulin == 0.0 && detailedBolusInfo.carbs == 0.0) {
// neither carbs nor bolus requested
aapsLogger.error("deliverTreatment: Invalid input: neither carbs nor insulin are set in treatment")
return PumpEnactResult(injector).success(false).enacted(false).bolusDelivered(0.0).carbsDelivered(0.0)
.comment(rh.gs(R.string.invalidinput))
} else if (detailedBolusInfo.insulin > 0.0) {
var isSuccess = true
val result = BehaviorSubject.createDefault(true)
val disposable = result.hide()
.subscribe {
isSuccess = it
}
mDisposables.add(patchManager.startCalculatorBolus(detailedBolusInfo)
.doOnSuccess {
mLastDataTime = System.currentTimeMillis()
}.subscribe({
result.onNext(it.isSuccess)
}, {
result.onNext(false)
})
)
val tr = detailedBolusInfo.let {
EventOverviewBolusProgress.Treatment(it.insulin, it.carbs.toInt(), it.bolusType === DetailedBolusInfo.BolusType.SMB, it.id)
}
do{
SystemClock.sleep(100)
if(patchManager.patchConnectionState.isConnected) {
val delivering = patchManager.bolusCurrent.nowBolus.injected
rxBus.send(EventOverviewBolusProgress.apply {
status = rh.gs(R.string.bolusdelivering, delivering)
percent = min((delivering / detailedBolusInfo.insulin * 100).toInt(), 100)
t = tr
})
}
}while(!patchManager.bolusCurrent.nowBolus.endTimeSynced && isSuccess)
rxBus.send(EventOverviewBolusProgress.apply {
status = rh.gs(R.string.bolusdelivered, detailedBolusInfo.insulin)
percent = 100
})
detailedBolusInfo.insulin = patchManager.bolusCurrent.nowBolus.injected.toDouble()
patchManager.addBolusToHistory(detailedBolusInfo)
disposable.dispose()
return if(isSuccess)
PumpEnactResult(injector).success(true)/*.enacted(true)*/.carbsDelivered(detailedBolusInfo.carbs).bolusDelivered(detailedBolusInfo.insulin)
else
PumpEnactResult(injector).success(false)/*.enacted(false)*/.carbsDelivered(0.0).bolusDelivered(detailedBolusInfo.insulin)
} else {
// no bolus required, carb only treatment
patchManager.addBolusToHistory(detailedBolusInfo)
return PumpEnactResult(injector).success(true).enacted(true).bolusDelivered(0.0)
.carbsDelivered(detailedBolusInfo.carbs).comment(rh.gs(info.nightscout.androidaps.core.R.string.ok))
}
}
override fun stopBolusDelivering() {
mDisposables.add(patchManager.stopNowBolus()
.subscribeOn(aapsSchedulers.io)
.observeOn(aapsSchedulers.main)
.subscribe { it ->
rxBus.send(EventOverviewBolusProgress.apply {
status = rh.gs(R.string.bolusdelivered, (it.injectedBolusAmount * 0.05f))
})
}
)
}
override fun setTempBasalAbsolute(absoluteRate: Double, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: PumpSync.TemporaryBasalType): PumpEnactResult {
aapsLogger.info(LTag.PUMP, "setTempBasalAbsolute - absoluteRate: ${absoluteRate.toFloat()}, durationInMinutes: ${durationInMinutes.toLong()}, enforceNew: $enforceNew")
if(patchManager.patchState.isNormalBasalAct){
mLastDataTime = System.currentTimeMillis()
val tb = TempBasal.createAbsolute(durationInMinutes.toLong(), absoluteRate.toFloat())
return patchManager.startTempBasal(tb)
.subscribeOn(aapsSchedulers.io)
.observeOn(aapsSchedulers.main)
.doOnSuccess {
pumpSync.syncTemporaryBasalWithPumpId(
timestamp = dateUtil.now(),
rate = absoluteRate,
duration = T.mins(durationInMinutes.toLong()).msecs(),
isAbsolute = true,
type = tbrType,
pumpId = dateUtil.now(),
pumpType = PumpType.EOFLOW_EOPATCH2,
pumpSerial = serialNumber()
)
aapsLogger.info(LTag.PUMP,"setTempBasalAbsolute - tbrCurrent:${readTBR()}")
}
.map { PumpEnactResult(injector).success(true).enacted(true).duration(durationInMinutes).absolute(absoluteRate).isPercent(false).isTempCancel(false) }
.onErrorReturnItem(PumpEnactResult(injector).success(false).enacted(false)
.comment("Internal error"))
.blockingGet()
}else{
aapsLogger.info(LTag.PUMP,"setTempBasalAbsolute - normal basal is not active")
return PumpEnactResult(injector).success(false).enacted(false)
}
}
override fun setTempBasalPercent(percent: Int, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: PumpSync.TemporaryBasalType): PumpEnactResult {
aapsLogger.info(LTag.PUMP,"setTempBasalPercent - percent: $percent, durationInMinutes: $durationInMinutes, enforceNew: $enforceNew")
if(patchManager.patchState.isNormalBasalAct && percent != 0){
mLastDataTime = System.currentTimeMillis()
val tb = TempBasal.createPercent(durationInMinutes.toLong(), percent)
return patchManager.startTempBasal(tb)
.subscribeOn(aapsSchedulers.io)
.observeOn(aapsSchedulers.main)
.doOnSuccess {
pumpSync.syncTemporaryBasalWithPumpId(
timestamp = dateUtil.now(),
rate = percent.toDouble(),
duration = T.mins(durationInMinutes.toLong()).msecs(),
isAbsolute = false,
type = tbrType,
pumpId = dateUtil.now(),
pumpType = PumpType.EOFLOW_EOPATCH2,
pumpSerial = serialNumber()
)
aapsLogger.info(LTag.PUMP,"setTempBasalPercent - tbrCurrent:${readTBR()}")
}
.map { PumpEnactResult(injector).success(true).enacted(true).duration(durationInMinutes).percent(percent).isPercent(true).isTempCancel(false) }
.onErrorReturnItem(PumpEnactResult(injector).success(false).enacted(false)
.comment("Internal error"))
.blockingGet()
}else{
aapsLogger.info(LTag.PUMP,"setTempBasalPercent - normal basal is not active")
return PumpEnactResult(injector).success(false).enacted(false)
}
}
override fun setExtendedBolus(insulin: Double, durationInMinutes: Int): PumpEnactResult {
aapsLogger.info(LTag.PUMP,"setExtendedBolus - insulin: $insulin, durationInMinutes: $durationInMinutes")
return patchManager.startQuickBolus(0f, insulin.toFloat(), BolusExDuration.ofRaw(durationInMinutes))
.doOnSuccess {
mLastDataTime = System.currentTimeMillis()
pumpSync.syncExtendedBolusWithPumpId(
timestamp = dateUtil.now(),
amount = insulin,
duration = T.mins(durationInMinutes.toLong()).msecs(),
isEmulatingTB = false,
pumpId = dateUtil.now(),
pumpType = PumpType.EOFLOW_EOPATCH2,
pumpSerial = serialNumber()
)
}
.map { PumpEnactResult(injector).success(true).enacted(true)}
.onErrorReturnItem(PumpEnactResult(injector).success(false).enacted(false).bolusDelivered(0.0)
.comment(rh.gs(info.nightscout.androidaps.core.R.string.error)))
.blockingGet()
}
override fun cancelTempBasal(enforceNew: Boolean): PumpEnactResult {
val tbrCurrent = readTBR()
if (tbrCurrent == null ) {
aapsLogger.debug(LTag.PUMP,"cancelTempBasal - TBR already false.")
return PumpEnactResult(injector).success(true).enacted(false)
}
if (!patchManager.patchState.isTempBasalActive) {
return if (pumpSync.expectedPumpState().temporaryBasal != null) {
PumpEnactResult(injector).success(true).enacted(true).isTempCancel(true)
}else
PumpEnactResult(injector).success(true).isTempCancel(true)
}
return patchManager.stopTempBasal()
.doOnSuccess {
mLastDataTime = System.currentTimeMillis()
aapsLogger.debug(LTag.PUMP,"cancelTempBasal - $it")
pumpSync.syncStopTemporaryBasalWithPumpId(
timestamp = dateUtil.now(),
endPumpId = dateUtil.now(),
pumpType = PumpType.EOFLOW_EOPATCH2,
pumpSerial = serialNumber()
)
}
.doOnError{
aapsLogger.error(LTag.PUMP,"cancelTempBasal() - $it")
}
.map { PumpEnactResult(injector).success(true).enacted(true).isTempCancel(true)}
.onErrorReturnItem(PumpEnactResult(injector).success(false).enacted(false)
.comment(rh.gs(info.nightscout.androidaps.core.R.string.error)))
.blockingGet()
}
override fun cancelExtendedBolus(): PumpEnactResult {
if(patchManager.patchState.isExtBolusActive){
return patchManager.stopExtBolus()
.doOnSuccess {
aapsLogger.debug(LTag.PUMP,"cancelExtendedBolus - success")
mLastDataTime = System.currentTimeMillis()
pumpSync.syncStopExtendedBolusWithPumpId(
timestamp = dateUtil.now(),
endPumpId = dateUtil.now(),
pumpType = PumpType.EOFLOW_EOPATCH2,
pumpSerial = serialNumber()
)
}
.map { PumpEnactResult(injector).success(true).enacted(true).isTempCancel(true)}
.onErrorReturnItem(PumpEnactResult(injector).success(false).enacted(false)
.comment(rh.gs(info.nightscout.androidaps.core.R.string.error)))
.blockingGet()
}else{
aapsLogger.debug(LTag.PUMP,"cancelExtendedBolus - nothing stops")
return if (pumpSync.expectedPumpState().extendedBolus != null) {
pumpSync.syncStopExtendedBolusWithPumpId(
timestamp = dateUtil.now(),
endPumpId = dateUtil.now(),
pumpType = PumpType.EOFLOW_EOPATCH2,
pumpSerial = serialNumber()
)
PumpEnactResult(injector).success(true).enacted(true).isTempCancel(true)
}else
PumpEnactResult(injector)
}
}
override fun getJSONStatus(profile: Profile, profileName: String, version: String): JSONObject {
return JSONObject()
}
override fun manufacturer(): ManufacturerType {
return ManufacturerType.Eoflow
}
override fun model(): PumpType {
return PumpType.EOFLOW_EOPATCH2
}
override fun serialNumber(): String {
return patchManager.patchConfig.patchSerialNumber
}
override val pumpDescription: PumpDescription
get() = mPumpDescription
override fun shortStatus(veryShort: Boolean): String {
if(patchManager.isActivated) {
var ret = ""
val activeTemp = pumpSync.expectedPumpState().temporaryBasal
if (activeTemp != null)
ret += "Temp: ${activeTemp.rate} U/hr"
val activeExtendedBolus = pumpSync.expectedPumpState().extendedBolus
if (activeExtendedBolus != null)
ret += "Extended: ${activeExtendedBolus.amount} U\n"
val reservoirStr = patchManager.patchState.remainedInsulin.let {
when {
it > 50f -> "50+ U"
it < 1f -> "0 U"
else -> "${it.roundToInt()} U"
}
}
ret += "Reservoir: $reservoirStr"
ret += "Battery: ${patchManager.patchState.batteryLevel()}"
return ret
}else{
return "EOPatch is not enabled."
}
}
override val isFakingTempsByExtendedBoluses: Boolean = false
override fun loadTDDs(): PumpEnactResult {
return PumpEnactResult(injector)
}
override fun canHandleDST(): Boolean {
return false
}
override fun getCustomActions(): List<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,34 @@
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, "%.2f", 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) +" "+ suffix!!
}
}
fun duration(value: Float): String {
return DURATION.apply(value)
}
fun duration(value: Float, suffix: String?): String {
return if (CommonUtils.isStringEmpty(suffix)) {
DURATION.apply(value)
} else {
DURATION.apply(value) +" " + suffix!!
}
}
}

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,77 @@
package info.nightscout.androidaps.plugins.pump.eopatch;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import java.util.Objects;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
public class OsAlarmService extends Service {
public static final int FOREGROUND_NOTIFICATION_ID = 34534554;
private CompositeDisposable compositeDisposable;
private boolean foreground = false;
@Override
public void onCreate() {
super.onCreate();
compositeDisposable = new CompositeDisposable();
startForeground();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground();
String action = null;
if (action == null) {
return Service.START_NOT_STICKY;
}
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
((NotificationManager) Objects.requireNonNull(getSystemService(Context.NOTIFICATION_SERVICE))).cancel(FOREGROUND_NOTIFICATION_ID);
compositeDisposable.dispose();
}
public synchronized void startForeground() {
if (!foreground) {
foreground = true;
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
public static void start(Context context) {
Intent intent = new Intent(context, OsAlarmService.class);
// context.startForegroundService(intent);
}
public static void notifyNotification(Context context, boolean isNetworkAvailable) {
notifyNotification(context);
}
public static void notifyNotification(Context context) {
// Notification builder = getNotification(context);
// ((NotificationManager) Objects.requireNonNull(context.getSystemService(Context.NOTIFICATION_SERVICE))).notify(FOREGROUND_NOTIFICATION_ID, builder);
}
}

View file

@ -0,0 +1,57 @@
@file:Suppress("unused")
package info.nightscout.androidaps.plugins.pump.eopatch
import android.os.SystemClock
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import io.reactivex.rxjava3.core.Scheduler
import io.reactivex.rxjava3.core.Single
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class RxAction @Inject constructor(
private val aapsSchedulers: AapsSchedulers,
private val aapsLogger: AAPSLogger
) {
enum class RxVoid {
INSTANCE
}
private fun sleep(millis: Long) {
if (millis <= 0)
return
SystemClock.sleep(millis)
}
private fun delay(delayMs: Long): Single<*> {
return if (delayMs <= 0) {
Single.just(1)
} else Single.timer(delayMs, TimeUnit.MILLISECONDS)
}
fun single(action: Runnable, delayMs: Long, scheduler: Scheduler): Single<*> {
return delay(delayMs)
.observeOn(scheduler)
.flatMap {
Single.fromCallable {
action.run()
RxVoid.INSTANCE
}
}
}
@JvmOverloads
fun runOnMainThread(action: Runnable, delayMs: Long = 0) {
single(action, delayMs, aapsSchedulers.main)
.subscribe({
},
{ e ->
aapsLogger.error("SilentObserver.onError() ignore", e)
})
}
}

View file

@ -0,0 +1,119 @@
package info.nightscout.androidaps.plugins.pump.eopatch.alarm
import android.content.Intent
import android.net.Uri
import info.nightscout.androidaps.plugins.pump.eopatch.R
import info.nightscout.androidaps.plugins.pump.eopatch.code.AlarmCategory
import java.util.*
import java.util.function.Function
import java.util.stream.Collectors
import java.util.stream.Stream
enum class AlarmCode(messageResId: Int) {
A002(R.string.string_a002), //"Empty reservoir"
A003(R.string.string_a003), //"Patch expired"
A004(R.string.string_a004), //"Occlusion"
A005(R.string.string_a005), //"Power on self test failure"
A007(R.string.string_a007), //"Inappropriate temperature"
A016(R.string.string_a016), //"Needle insertion Error"
A018(R.string.string_a018), //"Patch battery Error"
A019(R.string.string_a019), //"Patch battery Error"
A020(R.string.string_a020), //"Patch activation Error"
A022(R.string.string_a022), //"Patch Error"
A023(R.string.string_a023), //"Patch Error"
A034(R.string.string_a034), //"Patch Error"
A041(R.string.string_a041), //"Patch Error"
A042(R.string.string_a042), //"Patch Error"
A043(R.string.string_a043), //"Patch Error"
A044(R.string.string_a044), //"Patch Error"
A106(R.string.string_a106), //"Patch Error"
A107(R.string.string_a107), //"Patch Error"
A108(R.string.string_a108), //"Patch Error"
A116(R.string.string_a116), //"Patch Error"
A117(R.string.string_a117), //"Patch Error"
A118(R.string.string_a118), //"Patch Error"
B000(R.string.string_b000),
B001(R.string.string_b001), //"End of insulin suspend"
B003(R.string.string_b003), //"Low reservoir"
B005(R.string.string_b005), //"Patch operating life expired"
B006(R.string.string_b006), //"Patch will expire soon"
B012(R.string.string_b012), //"Incomplete Patch activation"
B018(R.string.string_b018); //"Patch battery low"
val type: Char = name[0]
val code: Int = name.substring(1).toInt()
val resId: Int = messageResId
val alarmCategory: AlarmCategory
get() = when (type) {
TYPE_ALARM -> AlarmCategory.ALARM
TYPE_ALERT -> AlarmCategory.ALERT
else -> AlarmCategory.NONE
}
val aeCode: Int
get() {
when (type) {
TYPE_ALARM -> return code + 100
TYPE_ALERT -> return code
}
return -1
}
val isPatchOccurrenceAlert: Boolean
get() = this == B003 || this == B005 || this == B006 || this == B018
val isPatchOccurrenceAlarm: Boolean
get() = this == A002 || this == A003 || this == A004 || this == A018 || this == A019 || this == A022
|| this == A023 || this == A034 || this == A041 || this == A042 || this == A043 || this == A044 || this == A106
|| this == A107 || this == A108 || this == A116 || this == A117 || this == A118
companion object {
const val TYPE_ALARM = 'A'
const val TYPE_ALERT = 'B'
private const val SCHEME = "alarmkey"
private const val ALARM_KEY_PATH = "alarmkey"
private const val QUERY_CODE = "alarmcode"
private val NAME_MAP = Stream.of(*values())
.collect(Collectors.toMap({ obj: AlarmCode -> obj.name }, Function.identity()))
fun fromStringToCode(name: String): AlarmCode? {
return NAME_MAP[name]
}
fun findByPatchAeCode(aeCode: Int): AlarmCode? {
return if (aeCode > 100) {
fromStringToCode(String.format(Locale.US, "A%03d", aeCode - 100))
} else fromStringToCode(String.format(Locale.US, "B%03d", aeCode))
}
@JvmStatic
fun getUri(alarmCode: AlarmCode): Uri {
return Uri.Builder()
.scheme(SCHEME)
.authority("info.nightscout.androidaps")
.path(ALARM_KEY_PATH)
.appendQueryParameter(QUERY_CODE, alarmCode.name)
.build()
}
@JvmStatic
fun getAlarmCode(uri: Uri): AlarmCode? {
if (SCHEME == uri.scheme && ALARM_KEY_PATH == uri.lastPathSegment) {
val code = uri.getQueryParameter(QUERY_CODE)
if (code.isNullOrBlank()) {
return null
}
return fromStringToCode(code)
}
return null
}
@JvmStatic
fun fromIntent(intent: Intent): AlarmCode? {
return intent.data?.let { getAlarmCode(it) }
}
}
}

View file

@ -0,0 +1,203 @@
package info.nightscout.androidaps.plugins.pump.eopatch.alarm
import android.content.Context
import android.content.Intent
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.CommandQueue
import info.nightscout.androidaps.interfaces.PumpSync
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
import info.nightscout.androidaps.plugins.pump.eopatch.EONotification
import info.nightscout.androidaps.plugins.pump.eopatch.EoPatchRxBus
import info.nightscout.androidaps.plugins.pump.eopatch.R
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.A005
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.A016
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.A020
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.B000
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.B001
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.B012
import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPatchManager
import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager
import info.nightscout.androidaps.plugins.pump.eopatch.code.AlarmCategory
import info.nightscout.androidaps.plugins.pump.eopatch.event.EventEoPatchAlarm
import info.nightscout.androidaps.plugins.pump.eopatch.ui.AlarmHelperActivity
import info.nightscout.androidaps.plugins.pump.eopatch.vo.Alarms
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.disposables.Disposable
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.max
interface IAlarmManager {
fun init()
fun restartAll()
}
@Singleton
class AlarmManager @Inject constructor() : IAlarmManager {
@Inject lateinit var patchManager: IPatchManager
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var commandQueue: CommandQueue
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var rxBus: RxBus
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var sp: SP
@Inject lateinit var context: Context
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var pm: IPreferenceManager
@Inject lateinit var mAlarmRegistry: IAlarmRegistry
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var pumpSync: PumpSync
private lateinit var mAlarmProcess: AlarmProcess
private var compositeDisposable: CompositeDisposable = CompositeDisposable()
private var alarmDisposable: Disposable? = null
@Inject
fun onInit() {
mAlarmProcess = AlarmProcess(patchManager, rxBus)
}
override fun init(){
alarmDisposable = EoPatchRxBus.listen(EventEoPatchAlarm::class.java)
.map { it.alarmCodes }
.doOnNext { aapsLogger.info(LTag.PUMP,"EventEoPatchAlarm Received") }
.concatMap {
Observable.fromArray(it)
.observeOn(aapsSchedulers.io)
.subscribeOn(aapsSchedulers.main)
.doOnNext { alarmCodes ->
alarmCodes.forEach { alarmCode ->
aapsLogger.info(LTag.PUMP,"alarmCode: ${alarmCode.name}")
val valid = isValid(alarmCode)
if (valid) {
if (alarmCode.alarmCategory == AlarmCategory.ALARM || alarmCode == B012) {
showAlarmDialog(alarmCode)
} else {
showNotification(alarmCode)
}
updateState(alarmCode, AlarmState.FIRED)
}else{
updateState(alarmCode, AlarmState.HANDLE)
}
}
}
}
.subscribe({}, { throwable: Throwable -> fabricPrivacy.logException(throwable) })
}
override fun restartAll() {
val now = System.currentTimeMillis()
@Suppress("UNCHECKED_CAST")
val occurredAlarm= pm.getAlarms().occurred.clone() as HashMap<AlarmCode, Alarms.AlarmItem>
@Suppress("UNCHECKED_CAST")
val registeredAlarm = pm.getAlarms().registered.clone() as HashMap<AlarmCode, Alarms.AlarmItem>
compositeDisposable.clear()
if(occurredAlarm.isNotEmpty()){
EoPatchRxBus.publish(EventEoPatchAlarm(occurredAlarm.keys))
}
if(registeredAlarm.isNotEmpty()){
registeredAlarm.forEach { raEntry ->
compositeDisposable.add(
mAlarmRegistry.add(raEntry.key, max(OS_REGISTER_GAP, raEntry.value.triggerTimeMilli - now))
.subscribe()
)
}
}
}
private fun isValid(code: AlarmCode): Boolean{
return when(code){
A005, A016, A020, B012 -> {
aapsLogger.info(LTag.PUMP,"Is $code valid? ${pm.getPatchConfig().hasMacAddress() && pm.getPatchConfig().lifecycleEvent.isSubStepRunning}")
pm.getPatchConfig().hasMacAddress() && pm.getPatchConfig().lifecycleEvent.isSubStepRunning
}
else -> {
aapsLogger.info(LTag.PUMP,"Is $code valid? ${pm.getPatchConfig().isActivated}")
pm.getPatchConfig().isActivated
}
}
}
private fun showAlarmDialog(alarmCode: AlarmCode){
val i = Intent(context, AlarmHelperActivity::class.java)
i.putExtra("soundid", R.raw.error)
i.putExtra("code", alarmCode.name)
i.putExtra("status", resourceHelper.gs(alarmCode.resId))
i.putExtra("title", resourceHelper.gs(R.string.string_alarm))
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(i)
}
private fun showNotification(alarmCode: AlarmCode, timeOffset: Long = 0L){
var alarmMsg = resourceHelper.gs(alarmCode.resId)
if(alarmCode == B000){
val expireTimeValue = pm.getPatchWakeupTimestamp() + TimeUnit.HOURS.toMillis(84)
val expireTimeString = SimpleDateFormat(resourceHelper.gs(R.string.date_format_yyyy_m_d_e_a_hh_mm_comma), Locale.US).format(expireTimeValue)
alarmMsg = resourceHelper.gs(alarmCode.resId, expireTimeString)
}
val notification = EONotification(Notification.EOELOW_PATCH_ALERTS + (alarmCode.aeCode + 10000), alarmMsg, Notification.URGENT)
notification.action(R.string.confirm) {
compositeDisposable.add(
Single.just(isValid(alarmCode))
.flatMap { isValid ->
return@flatMap if(isValid) mAlarmProcess.doAction(context, alarmCode)
else Single.just(IAlarmProcess.ALARM_HANDLED)
}
.subscribe { ret ->
if(ret == IAlarmProcess.ALARM_HANDLED){
if(alarmCode == B001){
pumpSync.syncStopTemporaryBasalWithPumpId(
timestamp = dateUtil.now(),
endPumpId = dateUtil.now(),
pumpType = PumpType.EOFLOW_EOPATCH2,
pumpSerial = patchManager.patchConfig.patchSerialNumber
)
}
updateState(alarmCode, AlarmState.HANDLE)
}else{
rxBus.send(EventNewNotification(notification))
}
})
}
notification.soundId = R.raw.error
notification.date = pm.getPatchConfig().patchWakeupTimestamp + TimeUnit.SECONDS.toMillis(timeOffset)
rxBus.send(EventNewNotification(notification))
}
private fun updateState(alarmCode: AlarmCode, state: AlarmState){
when(state){
AlarmState.REGISTER -> pm.getAlarms().register(alarmCode, 0)
AlarmState.FIRED -> pm.getAlarms().occurred(alarmCode)
AlarmState.HANDLE -> pm.getAlarms().handle(alarmCode)
}
pm.flushAlarms()
}
companion object {
private const val OS_REGISTER_GAP = 3 * 1000L
}
}

View file

@ -0,0 +1,124 @@
package info.nightscout.androidaps.plugins.pump.eopatch.alarm
import android.content.Context
import android.content.DialogInterface
import info.nightscout.androidaps.plugins.pump.eopatch.ui.EopatchActivity.Companion.createIntentForCheckConnection
import info.nightscout.androidaps.plugins.pump.eopatch.ui.EopatchActivity.Companion.createIntentForDiscarded
import info.nightscout.androidaps.plugins.pump.eopatch.ui.EopatchActivity.Companion.createIntentForCannulaInsertionError
import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPatchManager
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.*
import android.content.Intent
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.pump.eopatch.R
import info.nightscout.androidaps.plugins.pump.eopatch.ui.EopatchActivity
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BaseResponse
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TemperatureResponse
import info.nightscout.androidaps.plugins.pump.eopatch.event.EventDialog
import info.nightscout.androidaps.plugins.pump.eopatch.event.EventProgressDialog
import info.nightscout.androidaps.plugins.pump.eopatch.extension.takeOne
import info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs.CommonDialog
import io.reactivex.rxjava3.core.Single
import java.lang.Exception
import java.util.concurrent.Callable
interface IAlarmProcess {
fun doAction(context: Context, code: AlarmCode): Single<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)
A007 -> inappropriateTemperatureAction(context)
A016 -> needleInsertionErrorAction(context)
B000, B003, B018 -> Single.just(IAlarmProcess.ALARM_HANDLED)
B005, B006 -> Single.just(IAlarmProcess.ALARM_HANDLED)
B012 -> Single.just(IAlarmProcess.ALARM_HANDLED)
}
}
private fun startActivityWithSingleTop(context: Context, intent: Intent) {
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
context.startActivity(intent)
}
private fun showCommunicationFailedDialog(onConfirmed: Runnable) {
val dialog = CommonDialog().apply {
title = R.string.patch_communication_failed
message = R.string.patch_communication_check_helper_1
positiveBtn = R.string.string_communication_check
positiveListener = DialogInterface.OnClickListener { _, _ ->
onConfirmed.run()
dismiss()
}
}
rxBus.send(EventDialog(dialog, true))
}
private fun actionWithPatchCheckConnection(context: Context, action: Callable<Single<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, goHomeAfterDiscard = true, forceDiscard = 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): 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))
}
.flatMap { Single.just(IAlarmProcess.ALARM_HANDLED) }
}
}
private fun needleInsertionErrorAction(context: Context): Single<Int> {
return Single.fromCallable {
startActivityWithSingleTop(context, createIntentForCannulaInsertionError(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)
}
}
}

View file

@ -0,0 +1,164 @@
package info.nightscout.androidaps.plugins.pump.eopatch.alarm
import android.app.AlarmManager
import android.app.AlarmManager.AlarmClockInfo
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
import info.nightscout.androidaps.plugins.pump.eopatch.EoPatchRxBus
import info.nightscout.androidaps.plugins.pump.eopatch.OsAlarmReceiver
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode.Companion.getUri
import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager
import info.nightscout.androidaps.plugins.pump.eopatch.code.PatchLifecycle
import info.nightscout.androidaps.plugins.pump.eopatch.core.code.PatchAeCode
import info.nightscout.androidaps.plugins.pump.eopatch.event.EventEoPatchAlarm
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.disposables.Disposable
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
interface IAlarmRegistry {
fun add(alarmCode: AlarmCode, triggerAfter: Long, isFirst: Boolean = false): Maybe<AlarmCode>
fun add(patchAeCodes: Set<PatchAeCode>)
fun remove(alarmCode: 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
@Inject lateinit var aapsSchedulers: AapsSchedulers
private lateinit var mOsAlarmManager: AlarmManager
private var mDisposable: Disposable? = null
private var compositeDisposable: CompositeDisposable = CompositeDisposable()
@Inject fun onInit() {
mOsAlarmManager = mContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
mDisposable = pm.observePatchLifeCycle()
.observeOn(aapsSchedulers.main)
.subscribe {
when(it){
PatchLifecycle.REMOVE_NEEDLE_CAP -> {
val triggerAfter = pm.getPatchConfig().patchWakeupTimestamp + TimeUnit.HOURS.toMillis(1) - System.currentTimeMillis()
compositeDisposable.add(add(AlarmCode.A020, triggerAfter).subscribe())
}
PatchLifecycle.ACTIVATED -> {
}
PatchLifecycle.SHUTDOWN -> {
val sources = ArrayList<Maybe<*>>()
sources.add(Maybe.just(true))
pm.getAlarms().occurred.let{ occurredAlarms ->
if(occurredAlarms.isNotEmpty()){
occurredAlarms.keys.forEach { alarmCode ->
sources.add(
Maybe.just(alarmCode)
.observeOn(aapsSchedulers.main)
.doOnSuccess { rxBus.send(EventDismissNotification(Notification.EOELOW_PATCH_ALERTS + (alarmCode.aeCode + 10000))) }
)
}
}
}
pm.getAlarms().registered.let{ registeredAlarms ->
if(registeredAlarms.isNotEmpty()){
registeredAlarms.keys.forEach { alarmCode ->
sources.add(remove(alarmCode))
}
}
}
compositeDisposable.add(Maybe.concat(sources)
.subscribe {
pm.getAlarms().clear()
pm.flushAlarms()
}
)
}
else -> Unit
}
}
}
override fun add(alarmCode: AlarmCode, triggerAfter: Long, isFirst: Boolean): Maybe<AlarmCode> {
if(pm.getAlarms().occurred.containsKey(alarmCode)){
return Maybe.just(alarmCode)
}else {
val triggerTimeMilli = System.currentTimeMillis() + triggerAfter
pm.getAlarms().register(alarmCode, triggerAfter)
pm.flushAlarms()
if (triggerAfter <= 0L) {
EoPatchRxBus.publish(EventEoPatchAlarm(HashSet<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.findByPatchAeCode(patchAeCodeItem.aeValue) != null}
.observeOn(aapsSchedulers.main)
.filter { aeCodes -> AlarmCode.findByPatchAeCode(aeCodes.aeValue) != null }
.flatMapMaybe{aeCodeResponse -> add(AlarmCode.findByPatchAeCode(aeCodeResponse.aeValue)!!, 0L, true)}
.subscribe()
)
}
private fun registerOsAlarm(alarmCode: AlarmCode, triggerTime: Long): Maybe<AlarmCode> {
return Maybe.fromCallable {
cancelOsAlarmInternal(alarmCode)
val pendingIntent = createPendingIntent(alarmCode, 0)
mOsAlarmManager.setAlarmClock(AlarmClockInfo(triggerTime, pendingIntent), pendingIntent)
alarmCode
}
}
override fun remove(alarmCode: AlarmCode): Maybe<AlarmCode> {
return if(pm.getAlarms().registered.containsKey(alarmCode)) {
cancelOsAlarms(alarmCode)
.doOnSuccess {
pm.getAlarms().unregister(alarmCode)
pm.flushAlarms()
}
.map { alarmCode }
}else{
Maybe.just(alarmCode)
}
}
private fun cancelOsAlarms(vararg alarmCodes: AlarmCode): Maybe<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, flag)
}
}

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 const 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,120 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble;
import info.nightscout.androidaps.data.DetailedBolusInfo;
import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.BleConnectionState;
import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.PatchSelfTestResult;
import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.ScanList;
import info.nightscout.androidaps.plugins.pump.eopatch.code.BolusExDuration;
import info.nightscout.androidaps.plugins.pump.eopatch.code.DeactivationStatus;
import info.nightscout.androidaps.plugins.pump.eopatch.code.PatchLifecycle;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BasalScheduleSetResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BaseResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusStopResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.ComboBolusStopResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TempBasalScheduleSetResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TemperatureResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.BolusCurrent;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.NormalBasal;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchConfig;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchLifecycleEvent;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasal;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
public interface IPatchManager {
void init();
IPreferenceManager getPreferenceManager();
PatchConfig getPatchConfig();
boolean isActivated();
boolean isDeactivated();
Single<? extends BaseResponse> resumeBasal();
Observable<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 addBolusToHistory(DetailedBolusInfo originalDetailedBolusInfo);
void changeBuzzerSetting();
void changeReminderSetting();
void checkActivationProcess();
}

View file

@ -0,0 +1,443 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble;
import android.content.Context;
import android.content.Intent;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.data.DetailedBolusInfo;
import info.nightscout.androidaps.events.EventCustomActionsChanged;
import info.nightscout.androidaps.events.EventPumpStatusChanged;
import info.nightscout.androidaps.events.EventRefreshOverview;
import info.nightscout.androidaps.interfaces.ActivePlugin;
import info.nightscout.androidaps.interfaces.CommandQueue;
import info.nightscout.androidaps.interfaces.ProfileFunction;
import info.nightscout.androidaps.interfaces.PumpSync;
import info.nightscout.androidaps.interfaces.ResourceHelper;
import info.nightscout.androidaps.plugins.bus.RxBus;
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
import info.nightscout.androidaps.plugins.pump.eopatch.R;
import info.nightscout.androidaps.plugins.pump.eopatch.RxAction;
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode;
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmRegistry;
import info.nightscout.androidaps.plugins.pump.eopatch.code.BolusExDuration;
import info.nightscout.androidaps.plugins.pump.eopatch.code.DeactivationStatus;
import info.nightscout.androidaps.plugins.pump.eopatch.code.PatchLifecycle;
import info.nightscout.androidaps.plugins.pump.eopatch.code.SettingKeys;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BasalScheduleSetResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BaseResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusStopResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.ComboBolusStopResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TempBasalScheduleSetResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TemperatureResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.BleConnectionState;
import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.IPatchScanner;
import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.PatchScanner;
import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.PatchSelfTestResult;
import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.ScanList;
import info.nightscout.androidaps.plugins.pump.eopatch.event.EventPatchActivationNotComplete;
import info.nightscout.androidaps.plugins.pump.eopatch.ui.DialogHelperActivity;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.BolusCurrent;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.NormalBasal;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchConfig;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchLifecycleEvent;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasal;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.rx.AapsSchedulers;
import info.nightscout.shared.logging.AAPSLogger;
import info.nightscout.shared.sharedPreferences.SP;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
@Singleton
public class PatchManager implements IPatchManager {
@Inject PatchManagerImpl patchManager;
@Inject IPreferenceManager pm;
@Inject ProfileFunction profileFunction;
@Inject ActivePlugin activePlugin;
@Inject CommandQueue commandQueue;
@Inject AAPSLogger aapsLogger;
@Inject ResourceHelper resourceHelper;
@Inject RxBus rxBus;
@Inject Context context;
@Inject SP sp;
@Inject PumpSync pumpSync;
@Inject DateUtil dateUtil;
@Inject RxAction rxAction;
@Inject AapsSchedulers aapsSchedulers;
@Inject IAlarmRegistry alarmRegistry;
private IPatchScanner patchScanner;
private final CompositeDisposable mCompositeDisposable = new CompositeDisposable();
private Disposable mConnectingDisposable = null;
@Inject
public PatchManager() {}
@Inject
void onInit() {
patchScanner = new PatchScanner(context);
mCompositeDisposable.add(observePatchConnectionState()
.subscribe(bleConnectionState -> {
switch (bleConnectionState) {
case DISCONNECTED:
rxBus.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED));
rxBus.send(new EventRefreshOverview("Eopatch connection state: " + bleConnectionState.name(), true));
rxBus.send(new EventCustomActionsChanged());
stopObservingConnection();
break;
case CONNECTED:
rxBus.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTED));
rxBus.send(new EventRefreshOverview("Eopatch connection state: " + bleConnectionState.name(), true));
rxBus.send(new EventCustomActionsChanged());
stopObservingConnection();
break;
case CONNECTING:
mConnectingDisposable = Observable.interval(0, 1, TimeUnit.SECONDS)
.observeOn(aapsSchedulers.getMain())
.takeUntil(n -> getPatchConnectionState().isConnected() || n > 10 * 60)
.subscribe(n -> rxBus.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTING, n.intValue())));
break;
default:
stopObservingConnection();
}
})
);
mCompositeDisposable.add(rxBus
.toObservable(EventPatchActivationNotComplete.class)
.observeOn(aapsSchedulers.getIo())
.subscribeOn(aapsSchedulers.getMain())
.subscribe(eventPatchActivationNotComplete -> {
Intent i = new Intent(context, DialogHelperActivity.class);
i.putExtra("title", resourceHelper.gs(R.string.patch_activate_reminder_title));
i.putExtra("message", resourceHelper.gs(R.string.patch_activate_reminder_desc));
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
})
);
}
@Override
public void init() {
setConnection();
}
private void stopObservingConnection(){
if(mConnectingDisposable != null) {
mConnectingDisposable.dispose();
mConnectingDisposable = null;
}
}
@Override
public IPreferenceManager getPreferenceManager() {
return pm;
}
@Override
public PatchConfig getPatchConfig() {
return pm.getPatchConfig();
}
@Override
public Observable<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 boolean isDeactivated() {
return pm.getPatchConfig().isDeactivated();
}
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)
.doOnSuccess(success -> {
// if (success) {
// pumpSync.insertTherapyEventIfNewWithTimestamp(
// getPatchConfig().getPatchWakeupTimestamp(),
// DetailedBolusInfo.EventType.CANNULA_CHANGE,
// null,
// null,
// PumpType.EOFLOW_EOPATCH2,
// getPatchConfig().getPatchSerialNumber()
// );
// pumpSync.insertTherapyEventIfNewWithTimestamp(
// getPatchConfig().getPatchWakeupTimestamp(),
// DetailedBolusInfo.EventType.INSULIN_CHANGE,
// null,
// null,
// PumpType.EOFLOW_EOPATCH2,
// getPatchConfig().getPatchSerialNumber()
// );
// }
});
}
public Single<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> 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 addBolusToHistory(DetailedBolusInfo originalDetailedBolusInfo) {
DetailedBolusInfo detailedBolusInfo = originalDetailedBolusInfo.copy();
if(detailedBolusInfo.insulin > 0) {
pumpSync.syncBolusWithPumpId(
detailedBolusInfo.timestamp,
detailedBolusInfo.insulin,
detailedBolusInfo.getBolusType(),
dateUtil.now(),
PumpType.EOFLOW_EOPATCH2,
patchManager.pm.getPatchSerial()
);
}
if (detailedBolusInfo.carbs > 0) {
pumpSync.syncCarbsWithTimestamp(
detailedBolusInfo.getCarbsTimestamp() != null ? detailedBolusInfo.getCarbsTimestamp() : detailedBolusInfo.timestamp,
detailedBolusInfo.carbs,
null,
PumpType.USER,
patchManager.pm.getPatchSerial()
);
}
}
@Override
public void changeBuzzerSetting() {
boolean buzzer = sp.getBoolean(SettingKeys.Companion.getBUZZER_REMINDERS(), false);
if(pm.getPatchConfig().getInfoReminder() != buzzer) {
if (isActivated()) {
mCompositeDisposable.add(infoReminderSet(buzzer)
.observeOn(aapsSchedulers.getMain())
.subscribe(patchBooleanResponse -> {
pm.getPatchConfig().setInfoReminder(buzzer);
pm.flushPatchConfig();
}));
} else {
pm.getPatchConfig().setInfoReminder(buzzer);
pm.flushPatchConfig();
}
}
}
@Override
public void changeReminderSetting() {
int doseUnit = sp.getInt(SettingKeys.Companion.getLOW_RESERVOIR_REMINDERS(), 0);
int hours = sp.getInt(SettingKeys.Companion.getEXPIRATION_REMINDERS(), 0);
PatchConfig pc = pm.getPatchConfig();
if(pc.getLowReservoirAlertAmount() != doseUnit || pc.getPatchExpireAlertTime() != hours) {
if (isActivated()) {
mCompositeDisposable.add(setLowReservoir(doseUnit, hours)
.observeOn(aapsSchedulers.getMain())
.doOnSubscribe(disposable -> {
if(pc.getPatchExpireAlertTime() != hours){
Maybe.just(AlarmCode.B000)
.flatMap(alarmCode -> alarmRegistry.remove(alarmCode))
.flatMap(alarmCode -> alarmRegistry.add(alarmCode, (pc.getExpireTimestamp() - System.currentTimeMillis() - TimeUnit.HOURS.toMillis(hours)), false))
.subscribe();
}
})
.subscribe(patchBooleanResponse -> {
pc.setLowReservoirAlertAmount(doseUnit);
pc.setPatchExpireAlertTime(hours);
pm.flushPatchConfig();
}));
} else {
pc.setLowReservoirAlertAmount(doseUnit);
pc.setPatchExpireAlertTime(hours);
pm.flushPatchConfig();
}
}
}
@Override
public void checkActivationProcess(){
if(getPatchConfig().getLifecycleEvent().isSubStepRunning()
&& !pm.getAlarms().isOccurring(AlarmCode.A005)
&& !pm.getAlarms().isOccurring(AlarmCode.A020)) {
rxAction.runOnMainThread(() -> rxBus.send(new EventPatchActivationNotComplete()));
}
}
}

View file

@ -0,0 +1,737 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble;
import static android.content.Intent.ACTION_DATE_CHANGED;
import static android.content.Intent.ACTION_TIMEZONE_CHANGED;
import static android.content.Intent.ACTION_TIME_CHANGED;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import androidx.annotation.Nullable;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import javax.crypto.KeyAgreement;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.data.DetailedBolusInfo;
import info.nightscout.androidaps.interfaces.PumpSync;
import info.nightscout.androidaps.plugins.pump.eopatch.EoPatchRxBus;
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.ActivateTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.DeactivateTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.GetPatchInfoTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.InfoReminderTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.NeedleSensingTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.PauseBasalTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.PrimingTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.ResumeBasalTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.SelfTestTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.SetLowReservoirTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StartBondTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StartCalcBolusTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StartNormalBasalTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StartQuickBolusTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StartTempBasalTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StopComboBolusTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StopExtBolusTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StopNowBolusTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.StopTempBasalTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.SyncBasalHistoryTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.TaskBase;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.TaskFunc;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.UpdateConnectionTask;
import info.nightscout.androidaps.plugins.pump.eopatch.code.BolusExDuration;
import info.nightscout.androidaps.plugins.pump.eopatch.code.DeactivationStatus;
import info.nightscout.androidaps.plugins.pump.eopatch.code.SettingKeys;
import info.nightscout.androidaps.plugins.pump.eopatch.core.Patch;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BuzzerStop;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetTemperature;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.PublicKeySend;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.SequenceGet;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.StopAeBeep;
import info.nightscout.androidaps.plugins.pump.eopatch.core.code.BolusType;
import info.nightscout.androidaps.plugins.pump.eopatch.core.noti.AlarmNotification;
import info.nightscout.androidaps.plugins.pump.eopatch.core.noti.BaseNotification;
import info.nightscout.androidaps.plugins.pump.eopatch.core.noti.InfoNotification;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BasalScheduleSetResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BaseResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusStopResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.ComboBolusStopResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.KeyResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TempBasalScheduleSetResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TemperatureResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.BleConnectionState;
import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.IBleDevice;
import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.PatchSelfTestResult;
import info.nightscout.androidaps.plugins.pump.eopatch.event.EventEoPatchAlarm;
import info.nightscout.androidaps.plugins.pump.eopatch.ui.receiver.RxBroadcastReceiver;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.BolusCurrent;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.NormalBasal;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchConfig;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasal;
import info.nightscout.androidaps.utils.rx.AapsSchedulers;
import info.nightscout.shared.logging.AAPSLogger;
import info.nightscout.shared.logging.LTag;
import info.nightscout.shared.sharedPreferences.SP;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Scheduler;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.schedulers.Schedulers;
@Singleton
public class PatchManagerImpl{
@Inject IPreferenceManager pm;
@Inject Context context;
@Inject SP sp;
@Inject AAPSLogger aapsLogger;
@Inject AapsSchedulers aapsSchedulers;
@Inject PumpSync pumpSync;
@Inject StartBondTask START_BOND;
@Inject GetPatchInfoTask GET_PATCH_INFO;
@Inject SelfTestTask SELF_TEST;
@Inject PrimingTask START_PRIMING;
@Inject NeedleSensingTask START_NEEDLE_CHECK;
IBleDevice patch;
private final CompositeDisposable compositeDisposable;
private static final long DEFAULT_API_TIME_OUT = 10; // SECONDS
private final BuzzerStop BUZZER_STOP;
private final GetTemperature TEMPERATURE_GET;
private final StopAeBeep ALARM_ALERT_ERROR_BEEP_STOP;
private final PublicKeySend PUBLIC_KEY_SET;
private final SequenceGet SEQUENCE_GET;
@Inject
public PatchManagerImpl() {
compositeDisposable = new CompositeDisposable();
BUZZER_STOP = new BuzzerStop();
TEMPERATURE_GET = new GetTemperature();
ALARM_ALERT_ERROR_BEEP_STOP = new StopAeBeep();
PUBLIC_KEY_SET = new PublicKeySend();
SEQUENCE_GET = new SequenceGet();
}
@Inject
void onInit() {
patch = Patch.getInstance();
patch.init(context);
patch.setSeq(pm.getPatchConfig().getSeq15());
IntentFilter filter = new IntentFilter(ACTION_TIME_CHANGED);
filter.addAction(ACTION_DATE_CHANGED);
filter.addAction(ACTION_TIMEZONE_CHANGED);
Observable<Intent> dateTimeChanged = RxBroadcastReceiver.Companion.create(context, filter);
compositeDisposable.add(
Observable.combineLatest(patch.observeConnected(), pm.observePatchLifeCycle(),
(connected, lifeCycle) -> (connected && lifeCycle.isActivated()))
.subscribeOn(aapsSchedulers.getIo())
.filter(ok -> ok)
.observeOn(aapsSchedulers.getIo())
.doOnNext(v -> TaskBase.enqueue(TaskFunc.UPDATE_CONNECTION))
.retry()
.subscribe());
compositeDisposable.add(
Observable.combineLatest(patch.observeConnected(),
pm.observePatchLifeCycle().distinctUntilChanged(),
dateTimeChanged.startWith(Observable.just(new Intent())),
(connected, lifeCycle, value) -> (connected && lifeCycle.isActivated()))
.subscribeOn(aapsSchedulers.getIo())
.doOnNext(v -> aapsLogger.debug(LTag.PUMP,"Has the date or time changed? "+v))
.filter(ok -> ok)
.doOnNext(v -> TaskBase.enqueue(TaskFunc.SET_GLOBAL_TIME))
.doOnError(e -> aapsLogger.error(LTag.PUMP, "Failed to set EOPatch time."))
.retry()
.subscribe());
compositeDisposable.add(
patch.observeConnected()
.doOnNext(this::onPatchConnected)
.subscribe());
compositeDisposable.add(
pm.getPatchConfig().observe().doOnNext(config -> {
byte[] newKey = config.getSharedKey();
patch.updateEncryptionParam(newKey);
}).subscribe()
);
compositeDisposable.add(
EoPatchRxBus.INSTANCE.listen(EventEoPatchAlarm.class)
.filter(EventEoPatchAlarm::isFirst)
.filter(it -> !pm.getPatchConfig().isDeactivated())
.filter(it -> patch.getConnectionState().isConnected())
.concatMapIterable(EventEoPatchAlarm::getAlarmCodes)
.filter(AlarmCode::isPatchOccurrenceAlert)
.flatMap(it -> stopAeBeep(it.getAeCode()).toObservable())
.subscribe()
);
compositeDisposable.add(
EoPatchRxBus.INSTANCE.listen(EventEoPatchAlarm.class)
.filter(EventEoPatchAlarm::isFirst)
.filter(it -> !pm.getPatchConfig().isDeactivated())
.filter(it -> patch.getConnectionState().isConnected())
.concatMapIterable(EventEoPatchAlarm::getAlarmCodes)
.filter(AlarmCode::isPatchOccurrenceAlarm)
.flatMap(it -> pauseBasalImpl(0.0f, System.currentTimeMillis(), it).toObservable())
.subscribe()
);
monitorPatchNotification();
onConnectedUpdateSequence();
}
private void onPatchConnected(boolean connected) {
boolean activated = pm.getPatchConfig().isActivated();
boolean useEncryption = pm.getPatchConfig().getSharedKey() != null;
int doseUnit = sp.getInt(SettingKeys.Companion.getLOW_RESERVOIR_REMINDERS(), 0);
int hours = sp.getInt(SettingKeys.Companion.getEXPIRATION_REMINDERS(), 0);
boolean buzzer = sp.getBoolean(SettingKeys.Companion.getBUZZER_REMINDERS(), false);
PatchConfig pc = pm.getPatchConfig();
if (connected && activated && useEncryption) {
compositeDisposable.add(
SEQUENCE_GET.get()
.map(KeyResponse::getSequence)
.doOnSuccess(sequence -> {
if (sequence >= 0) {
saveSequence(sequence);
}
})
.flatMap(integer -> {
if(pc.getLowReservoirAlertAmount() != doseUnit || pc.getPatchExpireAlertTime() != hours) {
return setLowReservoir(doseUnit, hours)
.doOnSuccess(patchBooleanResponse -> {
pc.setLowReservoirAlertAmount(doseUnit);
pc.setPatchExpireAlertTime(hours);
pm.flushPatchConfig();
}).map(patchBooleanResponse -> true);
}
return Single.just(true);
})
.flatMap(ret -> {
if(pc.getInfoReminder() != buzzer) {
return infoReminderSet(buzzer)
.doOnSuccess(patchBooleanResponse -> {
pc.setInfoReminder(buzzer);
pm.flushPatchConfig();
}).map(patchBooleanResponse -> true);
}
return Single.just(true);
})
.subscribe());
}
if(!connected && activated){
pm.getPatchConfig().updatetDisconnectedTime();
}
}
private void monitorPatchNotification() {
compositeDisposable.addAll(
patch.observeAlarmNotification()
.subscribe(
this::onAlarmNotification,
throwable -> aapsLogger.error(LTag.PUMP, throwable.getMessage() != null ?
throwable.getMessage() : "AlarmNotification observation error")
),
patch.observeInfoNotification()
.filter(state -> pm.getPatchConfig().isActivated())
.subscribe(
this::onInfoNotification,
throwable -> aapsLogger.error(LTag.PUMP, throwable.getMessage() != null ?
throwable.getMessage() : "InfoNotification observation error")
)
);
}
private void onConnectedUpdateSequence() {
}
//==============================================================================================
// preference database update helper
//==============================================================================================
// synchronized lock
private final Object lock = new Object();
private void updatePatchConfig(Consumer<PatchConfig> consumer, boolean needSave) throws Throwable {
synchronized (lock) {
consumer.accept(pm.getPatchConfig());
if (needSave) {
pm.flushPatchConfig();
}
}
}
synchronized void updateBasal() {
NormalBasal normalBasal = pm.getNormalBasalManager().getNormalBasal();
if(normalBasal.updateNormalBasalIndex()) {
pm.flushNormalBasalManager();
}
}
public void connect() {
}
public void disconnect() {
}
/**
* getPatchConnection() 사용해야 한다.
* 아직 Life Cycle Activated 아님.
*
* Activation Process task #1 Get Patch Information from Patch
* Fragment: fragment_patch_connect_new
*/
public Single<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) {
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);
pumpSync.connectNewPump(true);
}
});
}
//==============================================================================================
// IPatchManager interface [NORMAL BASAL]
//==============================================================================================
@Inject
StartNormalBasalTask startNormalBasalTask;
public Single<BasalScheduleSetResponse> startBasal(NormalBasal basal) {
return startNormalBasalTask.start(basal)
.timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
}
@Inject
ResumeBasalTask resumeBasalTask;
public Single<? extends BaseResponse> resumeBasal() {
return resumeBasalTask.resume()
.timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
}
public Single<? extends BaseResponse> pauseBasal(float pauseDurationHour) {
return pauseBasalImpl(pauseDurationHour, 0, null)
.observeOn(SS)
.timeout(DEFAULT_API_TIME_OUT, TimeUnit.SECONDS);
}
//==============================================================================================
// IPatchManager implementation [NORMAL BASAL]
//==============================================================================================
@Inject
PauseBasalTask pauseBasalTask;
private Single<? extends BaseResponse> pauseBasalImpl(float pauseDurationHour, long alarmOccurredTime, @Nullable AlarmCode alarmCode) {
return pauseBasalTask.pause(pauseDurationHour, alarmOccurredTime, alarmCode);
}
//==============================================================================================
// IPatchManager interface [TEMP BASAL]
//==============================================================================================
@Inject
StartTempBasalTask startTempBasalTask;
public Single<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 infoNotification) {
if (infoNotification.isBolusRegAct()) {
BolusCurrent bolusCurrent = pm.getBolusCurrent();
Arrays.asList(BolusType.NOW, BolusType.EXT).forEach(type -> {
if (infoNotification.isBolusRegAct(type)) { // 완료되었어도 업데이트 필요.
int injectedPumpCount = infoNotification.getInjected(type);
int remainPumpCount = infoNotification.getRemain(type);
bolusCurrent.updateBolusFromPatch(type, injectedPumpCount, remainPumpCount);
}
});
pm.flushBolusCurrent();
}
}
@Inject
DeactivateTask deactivateTask;
// Patch Activation Tasks
public Single<DeactivationStatus> deactivate(long timeout, boolean force) {
return deactivateTask.run(force, timeout);
}
@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 Throwable {
patchStateManager.updatePatchState(PatchState.create(notification.patchState, System.currentTimeMillis()));
if (pm.getPatchConfig().isActivated()) {
if(!patch.isSeqReady()){
getSequence().subscribe();
}
updateBasal();
updateInjected(notification, true);
fetchPatchState();
}
}
private void onInfoNotification(InfoNotification notification) throws Throwable {
readBolusStatusFromNotification(notification);
updateInjected(notification, false);
if (notification.isBolusDone()) {
fetchPatchState();
}
}
void updateInjected(BaseNotification notification, boolean needSave) throws Throwable {
updatePatchConfig(patchConfig -> {
patchConfig.setInjectCount(notification.getTotalInjected());
patchConfig.setStandardBolusInjectCount(notification.getSB_CNT());
patchConfig.setExtendedBolusInjectCount(notification.getEB_CNT());
patchConfig.setBasalInjectCount(notification.getBasal_CNT());
}, needSave);
}
//==============================================================================================
// Security
//==============================================================================================
private static final String SECP256R1 = "secp256r1";
private static final String EC = "EC";
private static final String ECDH = "ECDH";
public Single<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);
return kpg.generateKeyPair();
});
}
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 {
KeyFactory kf = KeyFactory.getInstance(EC);
int mid = rawBytes.length / 2;
byte[] x = Arrays.copyOfRange(rawBytes, 0, mid);
byte[] y = Arrays.copyOfRange(rawBytes, mid, rawBytes.length);
ECPoint w = new ECPoint(new BigInteger(1, x), new BigInteger(1, y));
return (ECPublicKey) kf.generatePublic(new ECPublicKeySpec(w, ecParameterSpecForCurve(curveName)));
}
public static ECParameterSpec ecParameterSpecForCurve(String curveName) throws
NoSuchAlgorithmException, InvalidParameterSpecException {
AlgorithmParameters params = AlgorithmParameters.getInstance(EC);
params.init(new ECGenParameterSpec(curveName));
return params.getParameterSpec(ECParameterSpec.class);
}
public static byte[] generateSharedSecret(PrivateKey privateKey,
PublicKey publicKey) {
try {
KeyAgreement keyAgreement = KeyAgreement.getInstance(ECDH);
keyAgreement.init(privateKey);
keyAgreement.doPhase(publicKey, true);
return keyAgreement.generateSecret();
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
//==============================================================================================
// Single Scheduler all callback must be observed on
//==============================================================================================
private static final Scheduler SS = Schedulers.single();
public BleConnectionState getPatchConnectionState() {
return patch.getConnectionState();
}
public Observable<BleConnectionState> observePatchConnectionState() {
return patch.observeConnectionState();
}
public void updateMacAddress(String mac, boolean b){
patch.updateMacAddress(mac, b);
}
}

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.androidaps.utils.rx.AapsSchedulers;
import info.nightscout.shared.logging.AAPSLogger;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.pump.eopatch.core.code.BolusType;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.FetchAlarmTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.InternalSuspendedTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.ReadBolusFinishTimeTask;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.task.ReadTempBasalFinishTimeTask;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.BolusCurrent;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.NormalBasal;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasal;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.schedulers.Schedulers;
@Singleton
public class PatchStateManager {
@Inject IPreferenceManager pm;
@Inject ReadBolusFinishTimeTask readBolusFinishTimeTask;
@Inject ReadTempBasalFinishTimeTask readTempBasalFinishTimeTask;
@Inject InternalSuspendedTask internalSuspendedTask;
@Inject FetchAlarmTask FETCH_ALARM;
@Inject CommandQueue commandQueue;
@Inject AAPSLogger aapsLogger;
@Inject AapsSchedulers aapsSchedulers;
@Inject
public PatchStateManager() {
}
public synchronized void updatePatchState(PatchState newState) {
Maybe.fromCallable(() -> newState).observeOn(Schedulers.single())
.doOnSuccess(patchState -> updatePatchStateInner(patchState))
.observeOn(aapsSchedulers.getMain())
.doOnSuccess(patchState -> aapsLogger.debug(LTag.PUMP, patchState.toString()))
.subscribe();
}
/* Schedulers.io() */
public synchronized void updatePatchStateInner(PatchState newState) {
final PatchState oldState = pm.getPatchState();
int diff = newState.currentTime() - oldState.currentTime();
if (0 <= diff && diff < 10) {
/* 10초 안에 같은 PatchState update 시 skip */
if (oldState.equalState(newState)) {
return;
}
} else if (-5 < diff && diff < 0) {
/* 이전 State 가 새로운 State 를 덮어 쓰는 것을 방지 -4초 까지 */
return;
}
newState.setUpdatedTimestamp(System.currentTimeMillis());
if (newState.isNewAlertAlarm()) {
FETCH_ALARM.enqueue();
}
if (newState.isPatchInternalSuspended()){
onPatchInternalSuspended(newState);
}
/* Normal Basal --------------------------------------------------------------------------------------------- */
if (newState.isNormalBasalAct()) {
if (oldState.isNormalBasalPaused()) {
// Resume --> onBasalResume
onBasalResumeState();
} else if (oldState.isNormalBasalAct() == false) {
// Start --> onBasalStarted
}
} else if (oldState.isNormalBasalPaused() == false && newState.isNormalBasalPaused()) {
if (newState.isTempBasalAct()) {
} else {
// pause
}
}
/* Temp Basal ------------------------------------------------------------------------------------------- */
if (newState.isTempBasalAct()) {
if (oldState.isTempBasalAct() == false) {
// Start
onTempBasalStartState();
}
}
boolean tempBasalStopped = false;
boolean tempBasalFinished = false;
if (newState.isTempBasalDone() && !newState.isPatchInternalSuspended()) {
tempBasalFinished = true;
}
if (oldState.isTempBasalDone() == false) {
if (newState.isTempBasalDone()) {
tempBasalStopped = true;
onTempBasalDoneState();
} else if (oldState.isTempBasalAct() && newState.isTempBasalAct() == false) {
tempBasalStopped = true;
onTempBasalCancelState();
}
}
if (tempBasalStopped) {
if (newState.isNormalBasalAct()) {
if (!newState.isPatchInternalSuspended()) {
onNormalBasalResumed(tempBasalFinished);
}
}
}
if (newState.isTempBasalAct() == false && pm.getTempBasalManager().getStartedBasal() != null) {
pm.getTempBasalManager().updateBasalStopped();
}
/* Now Bolus -------------------------------------------------------------------------------------------- */
if (oldState.isNowBolusRegAct() == false && newState.isNowBolusRegAct() == true) {
// Start
} else if (oldState.isNowBolusDone() == false) {
if (oldState.isNowBolusRegAct() && newState.isNowBolusRegAct() == false) {
// Cancel
} else if (newState.isNowBolusDone()) {
// Done
}
}
BolusCurrent bolusCurrent = pm.getBolusCurrent();
if (newState.isNowBolusRegAct() == false && bolusCurrent.historyId(BolusType.NOW) > 0
&& bolusCurrent.endTimeSynced(BolusType.NOW)) {
bolusCurrent.clearBolus(BolusType.NOW);
}
/* Extended Bolus --------------------------------------------------------------------------------------- */
if (oldState.isExtBolusRegAct() == false && newState.isExtBolusRegAct() == true) {
// Start
} else if (oldState.isExtBolusDone() == false) {
if (oldState.isExtBolusRegAct() && newState.isExtBolusRegAct() == false) {
// Cancel
} else if (newState.isExtBolusDone()) {
// Done
}
}
if (newState.isExtBolusRegAct() == false && bolusCurrent.historyId(BolusType.EXT) > 0
&& bolusCurrent.endTimeSynced(BolusType.EXT)) {
bolusCurrent.clearBolus(BolusType.EXT);
}
/* Finish Time Sync and remained insulin update*/
/* Bolus Done -> update finish time */
if (Stream.of(BolusType.NOW, BolusType.EXT).anyMatch(type ->
newState.isBolusDone(type) && !bolusCurrent.endTimeSynced(type))) {
readBolusFinishTime();
}
/* TempBasal Done -> update finish time */
if (tempBasalFinished) {
readTempBasalFinishTime();
}
/* Remained Insulin update */
if (newState.getRemainedInsulin() != oldState.getRemainedInsulin()) {
pm.getPatchConfig().setRemainedInsulin(newState.getRemainedInsulin());
pm.flushPatchConfig();
}
pm.getPatchState().update(newState);
pm.flushPatchState();
}
private void onTempBasalStartState() {
TempBasal tempBasal = pm.getTempBasalManager().getStartedBasal();
if (tempBasal != null) {
pm.getPatchConfig().updateTempBasalStarted();
NormalBasal normalBasal = pm.getNormalBasalManager().getNormalBasal();
if (normalBasal != null) {
pm.getNormalBasalManager().updateBasalPaused();
}
pm.flushPatchConfig();
pm.flushNormalBasalManager();
}
}
void onTempBasalDoneState() {
TempBasal tempBasal = pm.getTempBasalManager().getStartedBasal();
if (tempBasal != null) {
pm.getTempBasalManager().updateBasalStopped();
pm.flushTempBasalManager();
}
}
private void onTempBasalCancelState() {
TempBasal tempBasal = pm.getTempBasalManager().getStartedBasal();
if (tempBasal != null) {
pm.getTempBasalManager().updateBasalStopped();
pm.flushTempBasalManager();
}
}
private void readBolusFinishTime() {
readBolusFinishTimeTask.enqueue();
}
private void readTempBasalFinishTime() {
readTempBasalFinishTimeTask.enqueue();
}
private synchronized void onBasalResumeState() {
if (!pm.getNormalBasalManager().isStarted()) {
long timestamp = System.currentTimeMillis();
onBasalResumed(timestamp + 1000);
}
}
void onNormalBasalResumed(boolean tempBasalFinished) {
NormalBasal normalBasal = pm.getNormalBasalManager().getNormalBasal();
if (normalBasal != null) {
pm.getNormalBasalManager().updateBasalStarted();
normalBasal.updateNormalBasalIndex();
pm.flushNormalBasalManager();;
}
}
public synchronized void onBasalResumed(long timestamp) {
if (!pm.getNormalBasalManager().isStarted()) {
pm.getNormalBasalManager().updateBasalStarted();
pm.getPatchConfig().updateNormalBasalStarted();
pm.getPatchConfig().setNeedSetBasalSchedule(false);
NormalBasal basal = pm.getNormalBasalManager().getNormalBasal();
if (basal != null) {
basal.updateNormalBasalIndex();
}
pm.flushPatchConfig();
pm.flushNormalBasalManager();
}
}
public synchronized void onBasalStarted(NormalBasal basal, long timestamp) {
if (basal != null) {
pm.getNormalBasalManager().updateBasalStarted();
basal.updateNormalBasalIndex();
}
pm.getPatchConfig().updateNormalBasalStarted(); // updateNormalBasalStarted 동일함...
pm.getPatchConfig().setNeedSetBasalSchedule(false);
pm.flushPatchConfig();
pm.flushNormalBasalManager();
}
private void onPatchInternalSuspended(PatchState state) {
boolean isNowBolusActive = state.isNowBolusActive();
boolean isExtBolusActive = state.isExtBolusActive();
boolean isTempBasalActive = state.isTempBasalActive();
if (isNowBolusActive || isExtBolusActive || isTempBasalActive) {
internalSuspendedTask.enqueue(isNowBolusActive, isExtBolusActive, isTempBasalActive);
}
}
}

View file

@ -0,0 +1,253 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.pump.eopatch.GsonHelper
import info.nightscout.androidaps.plugins.pump.eopatch.code.SettingKeys
import info.nightscout.androidaps.plugins.pump.eopatch.code.PatchLifecycle
import info.nightscout.androidaps.plugins.pump.eopatch.vo.*
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.rxjava3.core.Observable
import javax.inject.Inject
import javax.inject.Singleton
interface IPreferenceManager {
fun getPatchConfig(): PatchConfig
fun getPatchState(): PatchState
fun getBolusCurrent(): BolusCurrent
fun getNormalBasalManager(): NormalBasalManager
fun getTempBasalManager(): TempBasalManager
fun getAlarms(): Alarms
fun init()
fun flushPatchConfig()
fun flushPatchState()
fun flushBolusCurrent()
fun flushNormalBasalManager()
fun flushTempBasalManager()
fun flushAlarms()
fun updatePatchLifeCycle(event: PatchLifecycleEvent)
fun updatePatchState(newState: PatchState)
fun getPatchSerial(): String
fun getPatchMac(): String?
fun isActivated(): Boolean
fun setMacAddress(mac: String)
fun getPatchExpiredTime(): Long
fun setSharedKey(bytes: ByteArray?)
fun setSeq15(seq15: Int)
fun getSeq15(): Int
fun increaseSeq15()
fun getPatchWakeupTimestamp(): Long
fun observePatchLifeCycle(): Observable<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()
}
else -> Unit
}
}
override fun updatePatchState(newState: PatchState) {
mPatchState = newState
flushPatchState()
}
override fun getPatchSerial(): String {
return mPatchConfig.patchSerialNumber
}
override fun getPatchMac(): String? {
return mPatchConfig.macAddress
}
override fun isActivated(): Boolean {
return mPatchConfig.isActivated
}
override fun setMacAddress(mac: String) {
mPatchConfig.macAddress = mac
flushPatchConfig()
}
override fun getPatchExpiredTime(): Long {
return mPatchConfig.getPatchExpiredTime()
}
override fun setSharedKey(bytes: ByteArray?) {
mPatchConfig.sharedKey = bytes
}
override fun setSeq15(seq15: Int) {
mPatchConfig.seq15 = seq15
}
override fun getSeq15(): Int {
return mPatchConfig.seq15
}
override fun increaseSeq15() {
mPatchConfig.incSeq()
}
override fun getPatchWakeupTimestamp(): Long {
return mPatchConfig.patchWakeupTimestamp
}
override fun observePatchLifeCycle(): Observable<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,42 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.SetKey;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BaseResponse;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.NormalBasal;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchLifecycleEvent;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
@Singleton
public class ActivateTask extends TaskBase {
@Inject StartNormalBasalTask startBasalTask;
private final SetKey SET_KEY = new SetKey();
@Inject
public ActivateTask() {
super(TaskFunc.ACTIVATE);
}
public Single<Boolean> start() {
NormalBasal enabled = pm.getNormalBasalManager().getNormalBasal();
return isReady()
.concatMapSingle(v -> SET_KEY.setKey())
.doOnNext(this::checkResponse)
.firstOrError()
.observeOn(Schedulers.io())
.flatMap(v -> startBasalTask.start(enabled))
.doOnSuccess(this::onActivated)
.map(BaseResponse::isSuccess)
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "ActivateTask error"));
}
private void onActivated(BaseResponse response) {
pm.updatePatchLifeCycle(PatchLifecycleEvent.createActivated());
}
}

View file

@ -0,0 +1,106 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import info.nightscout.androidaps.plugins.pump.eopatch.code.BolusExDuration;
import info.nightscout.androidaps.plugins.pump.eopatch.AppConstant;
import info.nightscout.androidaps.plugins.pump.eopatch.core.code.BolusType;
import info.nightscout.androidaps.plugins.pump.eopatch.core.util.FloatAdjusters;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.BolusCurrent;
abstract class BolusTask extends TaskBase {
public BolusTask(TaskFunc func) {
super(func);
}
public void onQuickBolusStarted(float nowDoseU, float exDoseU, BolusExDuration exDuration) {
boolean now = (nowDoseU > 0);
boolean ext = (exDoseU > 0);
long startTimestamp = now ? System.currentTimeMillis() : 0;
long endTimestamp = startTimestamp + getPumpDuration(nowDoseU);
long nowHistoryID = 1L; //record no
long exStartTimestamp;
if (now) {
pm.getBolusCurrent().startNowBolus(nowHistoryID, nowDoseU, startTimestamp, endTimestamp);
}
if (ext) {
long estimatedExStartTimestamp;
if (now) {
exStartTimestamp = 0;
}
else {
estimatedExStartTimestamp = System.currentTimeMillis();
exStartTimestamp = estimatedExStartTimestamp;
}
long exEndTimestamp = exStartTimestamp + exDuration.milli();
long extHistoryID = 2L; //record no
pm.getBolusCurrent().startExtBolus(extHistoryID, exDoseU, exStartTimestamp,
exEndTimestamp, exDuration.milli());
}
pm.flushBolusCurrent();
}
public void onCalcBolusStarted(float nowDoseU) {
boolean now = (nowDoseU > 0);
long startTimestamp = now ? System.currentTimeMillis() : 0; // dm_1720
long endTimestamp = startTimestamp + getPumpDuration(nowDoseU);
long nowHistoryID = 1L; //record no
if (now) {
pm.getBolusCurrent().startNowBolus(nowHistoryID, nowDoseU, startTimestamp, endTimestamp);
}
pm.flushBolusCurrent();
}
public void updateNowBolusStopped(int injected) {
updateNowBolusStopped(injected, 0);
}
public void updateNowBolusStopped(int injected, long suspendedTimestamp) {
BolusCurrent bolusCurrent = pm.getBolusCurrent();
long nowID = bolusCurrent.historyId(BolusType.NOW);
if (nowID > 0 && !bolusCurrent.endTimeSynced(BolusType.NOW)) {
long stopTime = (suspendedTimestamp > 0) ? suspendedTimestamp : System.currentTimeMillis();
float injectedDoseU = FloatAdjusters.FLOOR2_BOLUS.apply(injected * AppConstant.INSULIN_UNIT_P);
bolusCurrent.getNowBolus().setInjected(injectedDoseU);
bolusCurrent.getNowBolus().setEndTimestamp(stopTime);
bolusCurrent.setEndTimeSynced(BolusType.NOW, true);
pm.flushBolusCurrent();
}
}
public void updateExtBolusStopped(int injected) {
updateExtBolusStopped(injected, 0);
}
public void updateExtBolusStopped(int injected, long suspendedTimestamp) {
BolusCurrent bolusCurrent = pm.getBolusCurrent();
long extID = bolusCurrent.historyId(BolusType.EXT);
if (extID > 0 && !bolusCurrent.endTimeSynced(BolusType.EXT)) {
long stopTime = (suspendedTimestamp > 0) ? suspendedTimestamp : System.currentTimeMillis();
float injectedDoseU = FloatAdjusters.FLOOR2_BOLUS.apply(injected * AppConstant.INSULIN_UNIT_P);
bolusCurrent.getExtBolus().setInjected(injectedDoseU);
bolusCurrent.getExtBolus().setEndTimestamp(stopTime);
bolusCurrent.setEndTimeSynced(BolusType.EXT, true);
pm.flushBolusCurrent();
}
}
private long getPumpDuration(float doseU) {
if (doseU > 0) {
long pumpDuration = pm.getPatchConfig().getPumpDurationSmallMilli();
return (long) ((doseU / AppConstant.BOLUS_UNIT_STEP) * pumpDuration);
}
return 0L;
}
}

View file

@ -0,0 +1,120 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager;
import info.nightscout.androidaps.plugins.pump.eopatch.code.DeactivationStatus;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.DeActivation;
import info.nightscout.androidaps.plugins.pump.eopatch.core.code.BolusType;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.BolusCurrent;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchLifecycleEvent;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasal;
import info.nightscout.androidaps.utils.rx.AapsSchedulers;
import info.nightscout.shared.logging.LTag;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class DeactivateTask extends TaskBase {
@Inject StopBasalTask stopBasalTask;
@Inject IPreferenceManager pm;
@Inject AapsSchedulers aapsSchedulers;
private final DeActivation DEACTIVATION;
@Inject
public DeactivateTask() {
super(TaskFunc.DEACTIVATE);
DEACTIVATION = new DeActivation();
}
public Single<DeactivationStatus> run(boolean forced, long timeout) {
return isReadyCheckActivated()
.timeout(timeout, TimeUnit.MILLISECONDS)
.concatMapSingle(v ->
DEACTIVATION.start()
.doOnSuccess(this::checkResponse)
.observeOn(aapsSchedulers.getIo())
.doOnSuccess(response -> onDeactivated()))
.map(response -> DeactivationStatus.of(response.isSuccess(), forced))
.firstOrError()
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "DeactivateTask error"))
.onErrorResumeNext(e -> {
if (forced) {
try {
onDeactivated();
} catch (Exception t) {
aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "DeactivateTask error");
}
}
return Single.just(DeactivationStatus.of(false, forced));
});
}
private Observable<TaskFunc> isReadyCheckActivated() {
if (pm.getPatchConfig().isActivated()) {
enqueue(TaskFunc.UPDATE_CONNECTION);
stopBasalTask.enqueue();
return isReady2();
}
return isReady();
}
private void onDeactivated() {
synchronized (lock) {
patch.updateMacAddress(null, false);
if (pm.getPatchConfig().getLifecycleEvent().isShutdown()) {
return;
}
cleanUpRepository();
pm.getNormalBasalManager().updateForDeactivation();
pm.updatePatchLifeCycle(PatchLifecycleEvent.createShutdown());
}
}
private void cleanUpRepository() {
updateNowBolusStopped();
updateExtBolusStopped();
updateTempBasalStopped();
}
private void updateTempBasalStopped() {
TempBasal tempBasal = pm.getTempBasalManager().getStartedBasal();
if (tempBasal != null) {
pm.getTempBasalManager().updateBasalStopped();
pm.flushTempBasalManager();
}
}
private void updateNowBolusStopped() {
BolusCurrent bolusCurrent = pm.getBolusCurrent();
long nowID = bolusCurrent.historyId(BolusType.NOW);
if (nowID > 0 && !bolusCurrent.endTimeSynced(BolusType.NOW)) {
bolusCurrent.setEndTimeSynced(BolusType.NOW, true);
pm.flushBolusCurrent();
}
}
private void updateExtBolusStopped() {
BolusCurrent bolusCurrent = pm.getBolusCurrent();
long extID = bolusCurrent.historyId(BolusType.EXT);
if (extID > 0 && !bolusCurrent.endTimeSynced(BolusType.EXT)) {
bolusCurrent.setEndTimeSynced(BolusType.EXT, true);
pm.flushBolusCurrent();
}
}
}

View file

@ -0,0 +1,47 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.bus.RxBus;
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmRegistry;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetErrorCodes;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.AeCodeResponse;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class FetchAlarmTask extends TaskBase {
@Inject RxBus rxBus;
@Inject IAlarmRegistry alarmRegistry;
private final GetErrorCodes ALARM_ALERT_ERROR_CODE_GET;
@Inject
public FetchAlarmTask() {
super(TaskFunc.FETCH_ALARM);
ALARM_ALERT_ERROR_CODE_GET = new GetErrorCodes();
}
public Single<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() != null) ? e.getMessage() : "FetchAlarmTask error"));
}
public synchronized void enqueue() {
boolean ready = (disposable == null || disposable.isDisposed());
if (ready) {
disposable = getPatchAlarm()
.timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
.subscribe();
}
}
}

View file

@ -0,0 +1,111 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetFirmwareVersion;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetLOT;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetModelName;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetPumpDuration;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetSerialNumber;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetWakeUpTime;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.SetGlobalTime;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BaseResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.FirmwareVersionResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.LotNumberResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.ModelNameResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PumpDurationResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.SerialNumberResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.WakeUpTimeResponse;
import java.util.Arrays;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
@Singleton
public class GetPatchInfoTask extends TaskBase {
@Inject UpdateConnectionTask updateConnectionTask;
private final SetGlobalTime SET_GLOBAL_TIME;
private final GetSerialNumber SERIAL_NUMBER_GET;
private final GetLOT LOT_NUMBER_GET;
private final GetFirmwareVersion FIRMWARE_VERSION_GET;
private final GetWakeUpTime WAKE_UP_TIME_GET;
private final GetPumpDuration PUMP_DURATION_GET;
private final GetModelName GET_MODEL_NAME;
@Inject
public GetPatchInfoTask() {
super(TaskFunc.GET_PATCH_INFO);
SET_GLOBAL_TIME = new SetGlobalTime();
SERIAL_NUMBER_GET = new GetSerialNumber();
LOT_NUMBER_GET = new GetLOT();
FIRMWARE_VERSION_GET = new GetFirmwareVersion();
WAKE_UP_TIME_GET = new GetWakeUpTime();
PUMP_DURATION_GET = new GetPumpDuration();
GET_MODEL_NAME = new GetModelName();
}
public Single<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)
.first(true);
return isReady()
.concatMapSingle(it -> tasks)
.firstOrError()
.observeOn(Schedulers.io())
.doOnSuccess(this::onPatchWakeupSuccess)
.doOnError(this::onPatchWakeupFailed)
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "GetPatchInfoTask error"));
}
private void onSerialNumberResponse(SerialNumberResponse v) {
pm.getPatchConfig().setPatchSerialNumber(v.getSerialNumber());
}
private void onLotNumberResponse(LotNumberResponse v) {
pm.getPatchConfig().setPatchLotNumber(v.getLotNumber());
}
private void onFirmwareResponse(FirmwareVersionResponse v) {
pm.getPatchConfig().setPatchFirmwareVersion(v.getFirmwareVersionString());
}
private void onWakeupTimeResponse(WakeUpTimeResponse v) {
pm.getPatchConfig().setPatchWakeupTimestamp(v.getTimeInMillis());
}
private void onPumpDurationResponse(PumpDurationResponse v) {
pm.getPatchConfig().setPumpDurationLargeMilli(v.getDurationL() * 100L);
pm.getPatchConfig().setPumpDurationMediumMilli(v.getDurationM() * 100L);
pm.getPatchConfig().setPumpDurationSmallMilli(v.getDurationS() * 100L);
}
private void onModelNameResponse(ModelNameResponse modelNameResponse) {
pm.getPatchConfig().setPatchModelName(modelNameResponse.getModelName());
}
private void onPatchWakeupSuccess(Boolean result) {
synchronized (lock) {
pm.flushPatchConfig();
}
}
private void onPatchWakeupFailed(Throwable e) {
patch.setSeq(-1);
pm.getPatchConfig().updateDeactivated();
pm.flushPatchConfig();
}
}

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.ble.IPreferenceManager;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.InfoReminderSet;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class InfoReminderTask extends TaskBase {
@Inject IPreferenceManager pm;
private final InfoReminderSet INFO_REMINDER_SET;
@Inject
public InfoReminderTask() {
super(TaskFunc.INFO_REMINDER);
INFO_REMINDER_SET = new InfoReminderSet();
}
/* alert delay 사용안함 */
public Single<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() != null) ? e.getMessage() : "InfoReminderTask error"));
}
public synchronized void enqueue() {
boolean ready = (disposable == null || disposable.isDisposed());
if (ready) {
disposable = set(pm.getPatchConfig().getInfoReminder())
.timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
.subscribe();
}
}
}

View file

@ -0,0 +1,124 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import android.os.SystemClock;
import info.nightscout.androidaps.interfaces.PumpSync;
import info.nightscout.androidaps.logging.UserEntryLogger;
import info.nightscout.androidaps.utils.userEntry.UserEntryMapper;
import info.nightscout.shared.logging.LTag;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.interfaces.CommandQueue;
import info.nightscout.shared.logging.AAPSLogger;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetInternalSuspendTime;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchInternalSuspendTimeResponse;
import info.nightscout.androidaps.queue.Callback;
import info.nightscout.androidaps.queue.commands.Command;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
@Singleton
public class InternalSuspendedTask extends BolusTask {
@Inject CommandQueue commandQueue;
@Inject AAPSLogger aapsLogger;
@Inject PumpSync pumpSync;
@Inject UserEntryLogger uel;
private final GetInternalSuspendTime INTERNAL_SUSPEND_TIME_GET;
private final BehaviorSubject<Boolean> bolusCheckSubject = BehaviorSubject.create();
private final BehaviorSubject<Boolean> extendedBolusCheckSubject = BehaviorSubject.create();
private final BehaviorSubject<Boolean> basalCheckSubject = BehaviorSubject.create();
@Inject
public InternalSuspendedTask() {
super(TaskFunc.INTERNAL_SUSPEND);
INTERNAL_SUSPEND_TIME_GET = new GetInternalSuspendTime();
}
private Observable<Boolean> getBolusSubject(){
return bolusCheckSubject.hide();
}
private Observable<Boolean> getExtendedBolusSubject(){
return extendedBolusCheckSubject.hide();
}
private Observable<Boolean> getBasalSubject(){
return basalCheckSubject.hide();
}
public Single<Long> start(boolean isNowBolusActive, boolean isExtBolusActive, boolean isTempBasalActive) {
if (isNowBolusActive || isExtBolusActive) {
enqueue(TaskFunc.READ_BOLUS_FINISH_TIME);
}
if (isTempBasalActive) {
enqueue(TaskFunc.READ_TEMP_BASAL_FINISH_TIME);
}
if (commandQueue.isRunning(Command.CommandType.BOLUS)) {
uel.log(UserEntryMapper.Action.CANCEL_BOLUS, UserEntryMapper.Sources.EOPatch2);
commandQueue.cancelAllBoluses(null);
SystemClock.sleep(650);
}
bolusCheckSubject.onNext(true);
if (pumpSync.expectedPumpState().getExtendedBolus() != null) {
uel.log(UserEntryMapper.Action.CANCEL_EXTENDED_BOLUS, UserEntryMapper.Sources.EOPatch2);
commandQueue.cancelExtended(new Callback() {
@Override
public void run() {
extendedBolusCheckSubject.onNext(true);
}
});
}else{
extendedBolusCheckSubject.onNext(true);
}
if (pumpSync.expectedPumpState().getTemporaryBasal() != null) {
uel.log(UserEntryMapper.Action.CANCEL_TEMP_BASAL, UserEntryMapper.Sources.EOPatch2);
commandQueue.cancelTempBasal(true, new Callback() {
@Override
public void run() {
basalCheckSubject.onNext(true);
}
});
}else{
basalCheckSubject.onNext(true);
}
return Observable.zip(getBolusSubject(), getExtendedBolusSubject(), getBasalSubject(),
(bolusReady, extendedBolusReady, basalReady) -> (bolusReady && extendedBolusReady && basalReady))
.filter(ready -> ready)
.flatMap(v -> isReady())
.concatMapSingle(v -> getInternalSuspendTime())
.firstOrError()
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "InternalSuspendedTask error"));
}
private Single<Long> getInternalSuspendTime() {
return INTERNAL_SUSPEND_TIME_GET.get()
.doOnSuccess(this::checkResponse)
.map(PatchInternalSuspendTimeResponse::getTotalSeconds);
}
public synchronized void enqueue(boolean isNowBolusActive, boolean isExtBolusActive, boolean isTempBasalActive) {
boolean ready = (disposable == null || disposable.isDisposed());
if (ready) {
disposable = start(isNowBolusActive, isExtBolusActive, isTempBasalActive)
.timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
.subscribe(v -> {
bolusCheckSubject.onNext(false);
extendedBolusCheckSubject.onNext(false);
basalCheckSubject.onNext(false);
});
}
}
}

View file

@ -0,0 +1,49 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode;
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmRegistry;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.StartNeedleCheck;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.UpdateConnection;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
import info.nightscout.shared.logging.LTag;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class NeedleSensingTask extends TaskBase {
@Inject IAlarmRegistry alarmRegistry;
StartNeedleCheck START_NEEDLE_CHECK;
UpdateConnection UPDATE_CONNECTION;
@Inject
public NeedleSensingTask() {
super(TaskFunc.NEEDLE_SENSING);
START_NEEDLE_CHECK = new StartNeedleCheck();
UPDATE_CONNECTION = new UpdateConnection();
}
public Single<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() != null) ? e.getMessage() : "NeedleSensingTask error"));
}
private void onResponse(PatchState v) {
if (v.isNeedNeedleSensing()) {
alarmRegistry.add(AlarmCode.A016, 0, false).subscribe();
} else {
alarmRegistry.remove(AlarmCode.A016).subscribe();
}
}
}

View file

@ -0,0 +1,163 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import android.os.SystemClock;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.interfaces.CommandQueue;
import info.nightscout.androidaps.interfaces.PumpSync;
import info.nightscout.androidaps.logging.UserEntryLogger;
import info.nightscout.androidaps.utils.userEntry.UserEntryMapper;
import info.nightscout.shared.logging.AAPSLogger;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode;
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmRegistry;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BasalPause;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
import info.nightscout.androidaps.queue.Callback;
import info.nightscout.androidaps.queue.commands.Command;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
@Singleton
public class PauseBasalTask extends BolusTask {
@Inject IAlarmRegistry alarmRegistry;
@Inject IPreferenceManager pm;
@Inject CommandQueue commandQueue;
@Inject AAPSLogger aapsLogger;
@Inject PumpSync pumpSync;
@Inject UserEntryLogger uel;
private final BasalPause BASAL_PAUSE;
private final BehaviorSubject<Boolean> bolusCheckSubject = BehaviorSubject.create();
private final BehaviorSubject<Boolean> extendedBolusCheckSubject = BehaviorSubject.create();
private final BehaviorSubject<Boolean> basalCheckSubject = BehaviorSubject.create();
@Inject
public PauseBasalTask() {
super(TaskFunc.PAUSE_BASAL);
BASAL_PAUSE = new BasalPause();
}
private Observable<Boolean> getBolusSubject(){
return bolusCheckSubject.hide();
}
private Observable<Boolean> getExtendedBolusSubject(){
return extendedBolusCheckSubject.hide();
}
private Observable<Boolean> getBasalSubject(){
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(null);
SystemClock.sleep(650);
}
bolusCheckSubject.onNext(true);
if (pumpSync.expectedPumpState().getExtendedBolus() != null) {
uel.log(UserEntryMapper.Action.CANCEL_EXTENDED_BOLUS, UserEntryMapper.Sources.EOPatch2);
commandQueue.cancelExtended(new Callback() {
@Override
public void run() {
extendedBolusCheckSubject.onNext(true);
}
});
}else{
extendedBolusCheckSubject.onNext(true);
}
if (pumpSync.expectedPumpState().getTemporaryBasal() != null) {
uel.log(UserEntryMapper.Action.CANCEL_TEMP_BASAL, UserEntryMapper.Sources.EOPatch2);
commandQueue.cancelTempBasal(true, new Callback() {
@Override
public void run() {
basalCheckSubject.onNext(true);
}
});
}else{
basalCheckSubject.onNext(true);
}
return Observable.zip(getBolusSubject(), getExtendedBolusSubject(), getBasalSubject(),
(bolusReady, extendedBolusReady, basalReady) -> (bolusReady && extendedBolusReady && basalReady))
.filter(ready -> ready)
.flatMap(v -> isReady())
.concatMapSingle(v -> getSuspendedTime(pausedTimestamp))
.concatMapSingle(suspendedTimestamp -> pauseBasal(pauseDurationHour, alarmCode))
.firstOrError()
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "PauseBasalTask error"));
}
private Single<Long> getSuspendedTime(long pausedTimestamp) {
return Single.just(pausedTimestamp);
}
private Single<PatchBooleanResponse> pauseBasal(float pauseDurationHour, @Nullable AlarmCode alarmCode) {
if(alarmCode == null) {
return BASAL_PAUSE.pause(pauseDurationHour)
.doOnSuccess(this::checkResponse)
.doOnSuccess(v -> onBasalPaused(pauseDurationHour, null));
}
// 정지 알람 발생 basal pause 커맨드 전달하지 않음 - 주입 정지 이력만 생성
onBasalPaused(pauseDurationHour, alarmCode);
return Single.just(new PatchBooleanResponse(true));
}
private void onBasalPaused(float pauseDurationHour, @Nullable AlarmCode alarmCode) {
if (!pm.getNormalBasalManager().isSuspended()) {
if (alarmCode != null) {
pm.getPatchConfig().updateNormalBasalPausedSilently();
}
else {
pm.getPatchConfig().updateNormalBasalPaused(pauseDurationHour);
}
pm.getNormalBasalManager().updateBasalSuspended();
pm.flushNormalBasalManager();
pm.flushPatchConfig();
if((alarmCode == null || alarmCode.getType() == AlarmCode.TYPE_ALERT) && pauseDurationHour != 0)
alarmRegistry.add(AlarmCode.B001, TimeUnit.MINUTES.toMillis((long)(pauseDurationHour * 60)), false).subscribe();
}
enqueue(TaskFunc.UPDATE_CONNECTION);
}
public synchronized void enqueue(float pauseDurationHour, long pausedTime, @Nullable AlarmCode alarmCode) {
boolean ready = (disposable == null || disposable.isDisposed());
if (ready) {
disposable = pause(pauseDurationHour, pausedTime, alarmCode)
.timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
.subscribe(v -> {
bolusCheckSubject.onNext(false);
extendedBolusCheckSubject.onNext(false);
basalCheckSubject.onNext(false);
});
}
}
}

View file

@ -0,0 +1,54 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.StartPriming;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.UpdateConnection;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
import io.reactivex.rxjava3.core.Observable;
@Singleton
public class PrimingTask extends TaskBase {
private final UpdateConnection UPDATE_CONNECTION;
private final StartPriming START_PRIMING;
@Inject
public PrimingTask() {
super(TaskFunc.PRIMING);
UPDATE_CONNECTION = new UpdateConnection();
START_PRIMING = new StartPriming();
}
public Observable<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() != null) ? e.getMessage() : "PrimingTask error"));
}
private Observable<Long> observePrimingSuccess(long count) {
return Observable.merge(
Observable.interval(1, TimeUnit.SECONDS).take(count + 10)
.map(v -> v * 3)
.doOnNext(v -> {
if (v >= count) {
throw new Exception("Priming failed");
}
}),
Observable.interval(3, TimeUnit.SECONDS)
.concatMapSingle(v -> UPDATE_CONNECTION.get())
.map(response -> PatchState.Companion.create(response.getPatchState(), System.currentTimeMillis()))
.filter(PatchState::isPrimingSuccess)
.map(result -> count)
);
}
}

View file

@ -0,0 +1,63 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BolusFinishTimeGet;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusFinishTimeResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.code.BolusType;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.BolusCurrent;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchState;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class ReadBolusFinishTimeTask extends BolusTask {
private final BolusFinishTimeGet BOLUS_FINISH_TIME_GET;
@Inject
public ReadBolusFinishTimeTask() {
super(TaskFunc.READ_BOLUS_FINISH_TIME);
BOLUS_FINISH_TIME_GET = new BolusFinishTimeGet();
}
Single<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() != null) ? e.getMessage() : "ReadBolusFinishTimeTask error"));
}
void onResponse(BolusFinishTimeResponse response) {
PatchState patchState = pm.getPatchState();
BolusCurrent bolusCurrent = pm.getBolusCurrent();
long nowHistoryID = bolusCurrent.historyId(BolusType.NOW);
long extHistoryID = bolusCurrent.historyId(BolusType.EXT);
if (nowHistoryID > 0 && patchState.isBolusDone(BolusType.NOW) && response.getNowBolusFinishTime() > 0) {
bolusCurrent.setEndTimeSynced(BolusType.NOW, true);
enqueue(TaskFunc.STOP_NOW_BOLUS);
}
if (extHistoryID > 0 && patchState.isBolusDone(BolusType.EXT) && response.getExtBolusFinishTime() > 0) {
bolusCurrent.setEndTimeSynced(BolusType.EXT, true);
enqueue(TaskFunc.STOP_EXT_BOLUS);
}
pm.flushBolusCurrent();
}
public synchronized void enqueue() {
boolean ready = (disposable == null || disposable.isDisposed());
if (ready) {
disposable = read()
.timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
.subscribe();
}
}
}

View file

@ -0,0 +1,45 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.TempBasalFinishTimeGet;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TempBasalFinishTimeResponse;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class ReadTempBasalFinishTimeTask extends TaskBase {
private final TempBasalFinishTimeGet TEMP_BASAL_FINISH_TIME_GET;
@Inject
public ReadTempBasalFinishTimeTask() {
super(TaskFunc.READ_TEMP_BASAL_FINISH_TIME);
TEMP_BASAL_FINISH_TIME_GET = new TempBasalFinishTimeGet();
}
public Single<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() != null) ? e.getMessage() : "ReadTempBasalFinishTimeTask error"));
}
private void onResponse(TempBasalFinishTimeResponse response) {
}
public synchronized void enqueue() {
boolean ready = (disposable == null || disposable.isDisposed());
if (ready) {
disposable = read()
.timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
.subscribe();
}
}
}

View file

@ -0,0 +1,59 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmCode;
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmRegistry;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.PatchStateManager;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BasalResume;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BaseResponse;
import java.sql.SQLException;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.NormalBasal;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class ResumeBasalTask extends TaskBase {
@Inject IAlarmRegistry alarmRegistry;
@Inject StartNormalBasalTask startNormalBasalTask;
@Inject PatchStateManager patchStateManager;
private final BasalResume BASAL_RESUME;
@Inject
public ResumeBasalTask() {
super(TaskFunc.RESUME_BASAL);
BASAL_RESUME = new BasalResume();
}
public synchronized Single<? extends BaseResponse> resume() {
if (pm.getPatchConfig().getNeedSetBasalSchedule()) {
return startNormalBasalTask.start(pm.getNormalBasalManager().getNormalBasal());
}
return isReady().concatMapSingle(v -> BASAL_RESUME.resume())
.doOnNext(this::checkResponse)
.firstOrError()
.doOnSuccess(v -> onResumeResponse(v))
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "ResumeBasalTask error"));
}
private void onResumeResponse(PatchBooleanResponse v) {
if (v.isSuccess()) {
patchStateManager.onBasalResumed(v.getTimestamp() + 1000);
alarmRegistry.remove(AlarmCode.B001).subscribe();
}
enqueue(TaskFunc.UPDATE_CONNECTION);
}
@Override
protected void preCondition() throws Exception {
checkPatchActivated();
checkPatchConnected();
}
}

View file

@ -0,0 +1,50 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.pump.eopatch.core.scan.PatchSelfTestResult;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetGlobalTime;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetTemperature;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetVoltageLevelB4Priming;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BatteryVoltageLevelPairingResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.GlobalTimeResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TemperatureResponse;
import java.util.Arrays;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class SelfTestTask extends TaskBase {
private final GetTemperature TEMPERATURE_GET;
private final GetVoltageLevelB4Priming BATTERY_LEVEL_GET_BEFORE_PRIMING;
private final GetGlobalTime GET_GLOBAL_TIME;
@Inject
public SelfTestTask() {
super(TaskFunc.SELF_TEST);
TEMPERATURE_GET = new GetTemperature();
BATTERY_LEVEL_GET_BEFORE_PRIMING = new GetVoltageLevelB4Priming();
GET_GLOBAL_TIME = new GetGlobalTime();
}
public Single<PatchSelfTestResult> start() {
Single<PatchSelfTestResult> tasks = Single.concat(Arrays.asList(
TEMPERATURE_GET.get()
.map(TemperatureResponse::getResult),
BATTERY_LEVEL_GET_BEFORE_PRIMING.get()
.map(BatteryVoltageLevelPairingResponse::getResult),
GET_GLOBAL_TIME.get(false)
.map(GlobalTimeResponse::getResult)))
.filter(result -> result != PatchSelfTestResult.TEST_SUCCESS)
.first(PatchSelfTestResult.TEST_SUCCESS);
return isReady()
.concatMapSingle(v -> tasks)
.firstOrError()
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "SelfTestTask error"));
}
}

View file

@ -0,0 +1,73 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.GetGlobalTime;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.SetGlobalTime;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.GlobalTimeResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class SetGlobalTimeTask extends TaskBase {
private final SetGlobalTime SET_GLOBAL_TIME;
private final GetGlobalTime GET_GLOBAL_TIME;
@Inject
public SetGlobalTimeTask() {
super(TaskFunc.SET_GLOBAL_TIME);
SET_GLOBAL_TIME = new SetGlobalTime();
GET_GLOBAL_TIME = new GetGlobalTime();
}
public Single<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() != null) ? e.getMessage() : "SetGlobalTimeTask error"));
}
private void checkPatchTime(GlobalTimeResponse response) throws Exception {
long newMilli = System.currentTimeMillis();
long oldMilli = response.getGlobalTimeInMilli();
long oldOffset = response.getTimeZoneOffset();
int offset = TimeZone.getDefault().getOffset(newMilli);
int minutes = (int) TimeUnit.MILLISECONDS.toMinutes(offset);
int newOffset = minutes / 15;
long diff = Math.abs(oldMilli - newMilli);
if (diff > 60000 || oldOffset != newOffset) {
aapsLogger.debug(LTag.PUMPCOMM, String.format("checkPatchTime %s %s %s", diff, oldOffset, newOffset));
return;
}
throw new Exception("No time set required");
}
public synchronized void enqueue() {
boolean ready = (disposable == null || disposable.isDisposed());
if (ready) {
disposable = set()
.timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
.subscribe(v -> {}, e -> {}); // Exception 사용하기에...
}
}
private void onSuccess() {
}
}

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.IPreferenceManager;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.SetLowReservoirLevelAndExpireAlert;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class SetLowReservoirTask extends TaskBase {
@Inject IPreferenceManager pm;
private final SetLowReservoirLevelAndExpireAlert SET_LOW_RESERVOIR_N_EXPIRE_ALERT;
@Inject
public SetLowReservoirTask() {
super(TaskFunc.LOW_RESERVOIR);
SET_LOW_RESERVOIR_N_EXPIRE_ALERT = new SetLowReservoirLevelAndExpireAlert();
}
public Single<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() != null) ? e.getMessage() : "SetLowReservoirTask error"));
}
public synchronized void enqueue() {
int alertTime = pm.getPatchConfig().getPatchExpireAlertTime();
int alertSetting = pm.getPatchConfig().getLowReservoirAlertAmount();
boolean ready = (disposable == null || disposable.isDisposed());
if (ready) {
disposable = set(alertSetting, alertTime)
.timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
.subscribe();
}
}
@Override
protected void preCondition() throws Exception {
checkPatchConnected();
}
}

View file

@ -0,0 +1,55 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import android.bluetooth.BluetoothDevice;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.StartBonding;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.reactivex.rxjava3.core.Single;
import static info.nightscout.androidaps.plugins.pump.eopatch.core.api.StartBonding.OPTION_NUMERIC;
@Singleton
public class StartBondTask extends TaskBase {
private final StartBonding START_BOND;
@Inject
public StartBondTask() {
super(TaskFunc.START_BOND);
START_BOND = new StartBonding();
}
public Single<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() != null) ? e.getMessage() : "StartBondTask error");
})
.firstOrError();
}
private synchronized void prefSetMacAddress(String mac) {
pm.getPatchConfig().setMacAddress(mac);
}
}

View file

@ -0,0 +1,46 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import info.nightscout.androidaps.data.DetailedBolusInfo;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BolusStart;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusResponse;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class StartCalcBolusTask extends BolusTask {
private final BolusStart NOW_BOLUS_START;
@Inject
public StartCalcBolusTask() {
super(TaskFunc.START_CALC_BOLUS);
NOW_BOLUS_START = new BolusStart();
}
public Single<? extends BolusResponse> start(DetailedBolusInfo detailedBolusInfo) {
return isReady().concatMapSingle(v -> startBolusImpl((float)detailedBolusInfo.insulin))
.doOnNext(this::checkResponse)
.firstOrError()
.doOnSuccess(v -> onSuccess((float)detailedBolusInfo.insulin))
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StartCalcBolusTask error"));
}
private Single<? extends BolusResponse> startBolusImpl(float nowDoseU) {
return NOW_BOLUS_START.start(nowDoseU);
}
private void onSuccess(float nowDoseU) {
onCalcBolusStarted(nowDoseU);
enqueue(TaskFunc.UPDATE_CONNECTION);
}
@Override
protected void preCondition() throws Exception {
checkPatchConnected();
}
}

View file

@ -0,0 +1,53 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.PatchStateManager;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BasalScheduleSetBig;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BasalScheduleSetResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.NormalBasal;
import info.nightscout.androidaps.utils.rx.AapsSchedulers;
import info.nightscout.shared.logging.LTag;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class StartNormalBasalTask extends TaskBase {
private final BasalScheduleSetBig BASAL_SCHEDULE_SET_BIG;
@Inject PatchStateManager patchStateManager;
@Inject AapsSchedulers aapsSchedulers;
@Inject
public StartNormalBasalTask() {
super(TaskFunc.START_NORMAL_BASAL);
BASAL_SCHEDULE_SET_BIG = new BasalScheduleSetBig();
}
public Single<BasalScheduleSetResponse> start(NormalBasal basal) {
return isReady().concatMapSingle(v -> startJob(basal)).firstOrError();
}
public Single<BasalScheduleSetResponse> startJob(NormalBasal basal) {
return BASAL_SCHEDULE_SET_BIG.set(basal.getDoseUnitPerSegmentArray())
.doOnSuccess(this::checkResponse)
.observeOn(aapsSchedulers.getIo())
.doOnSuccess(v -> onStartNormalBasalResponse(v, basal))
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StartNormalBasalTask error"));
}
private void onStartNormalBasalResponse(BasalScheduleSetResponse response, NormalBasal basal) {
long timeStamp = response.getTimestamp();
patchStateManager.onBasalStarted(basal, timeStamp+1000);
pm.getNormalBasalManager().setNormalBasal(basal);
pm.flushNormalBasalManager();
enqueue(TaskFunc.UPDATE_CONNECTION);
}
@Override
protected void preCondition() throws Exception {
checkPatchConnected();
}
}

View file

@ -0,0 +1,61 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.pump.eopatch.code.BolusExDuration;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BolusStart;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.ComboBolusStart;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.ExtBolusStart;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusResponse;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class StartQuickBolusTask extends BolusTask {
private final BolusStart NOW_BOLUS_START;
private final ExtBolusStart EXT_BOLUS_START;
private final ComboBolusStart COMBO_BOLUS_START;
@Inject
public StartQuickBolusTask() {
super(TaskFunc.START_QUICK_BOLUS);
NOW_BOLUS_START = new BolusStart();
EXT_BOLUS_START = new ExtBolusStart();
COMBO_BOLUS_START = new ComboBolusStart();
}
public Single<? extends BolusResponse> start(float nowDoseU, float exDoseU,
BolusExDuration exDuration) {
return isReady().concatMapSingle(v -> startBolusImpl(nowDoseU, exDoseU, exDuration))
.doOnNext(this::checkResponse)
.firstOrError()
.doOnSuccess(v -> onSuccess(nowDoseU, exDoseU, exDuration))
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StartQuickBolusTask error"));
}
private Single<? extends BolusResponse> startBolusImpl(float nowDoseU, float exDoseU,
BolusExDuration exDuration) {
if (nowDoseU > 0 && exDoseU > 0) {
return COMBO_BOLUS_START.start(nowDoseU, exDoseU, exDuration.getMinute());
} else if (exDoseU > 0) {
return EXT_BOLUS_START.start(exDoseU, exDuration.getMinute());
} else {
return NOW_BOLUS_START.start(nowDoseU);
}
}
private void onSuccess(float nowDoseU, float exDoseU, BolusExDuration exDuration) {
onQuickBolusStarted(nowDoseU, exDoseU, exDuration);
enqueue(TaskFunc.UPDATE_CONNECTION);
}
@Override
protected void preCondition() throws Exception {
//checkPatchActivated();
checkPatchConnected();
}
}

View file

@ -0,0 +1,48 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.TempBasalScheduleStart;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.TempBasalScheduleSetResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasal;
import info.nightscout.androidaps.utils.rx.AapsSchedulers;
import info.nightscout.shared.logging.LTag;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class StartTempBasalTask extends TaskBase {
@Inject IPreferenceManager pm;
@Inject AapsSchedulers aapsSchedulers;
private final TempBasalScheduleStart TEMP_BASAL_SCHEDULE_START;
@Inject
public StartTempBasalTask() {
super(TaskFunc.START_TEMP_BASAL);
TEMP_BASAL_SCHEDULE_START = new TempBasalScheduleStart();
}
public Single<TempBasalScheduleSetResponse> start(TempBasal tempBasal) {
return isReady()
.concatMapSingle(v -> TEMP_BASAL_SCHEDULE_START.start(tempBasal.getDurationMinutes(), tempBasal.getDoseUnitPerHour(), tempBasal.getPercent()))
.doOnNext(this::checkResponse)
.firstOrError()
.observeOn(aapsSchedulers.getIo())
.doOnSuccess(v -> onTempBasalStarted(tempBasal))
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StartTempBasalTask error"));
}
private void onTempBasalStarted(TempBasal tempBasal) {
pm.getTempBasalManager().updateBasalRunning(tempBasal);
pm.flushTempBasalManager();
enqueue(TaskFunc.UPDATE_CONNECTION);
}
@Override
protected void preCondition() throws Exception {
checkPatchConnected();
}
}

View file

@ -0,0 +1,124 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import android.os.SystemClock;
import info.nightscout.androidaps.interfaces.PumpSync;
import info.nightscout.androidaps.logging.UserEntryLogger;
import info.nightscout.androidaps.utils.userEntry.UserEntryMapper;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BasalStop;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BasalStopResponse;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.interfaces.CommandQueue;
import info.nightscout.shared.logging.AAPSLogger;
import info.nightscout.androidaps.queue.Callback;
import info.nightscout.androidaps.queue.commands.Command;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
@Singleton
public class StopBasalTask extends TaskBase {
@Inject IPreferenceManager pm;
@Inject CommandQueue commandQueue;
@Inject AAPSLogger aapsLogger;
@Inject PumpSync pumpSync;
@Inject UserEntryLogger uel;
private final BasalStop BASAL_STOP;
private final BehaviorSubject<Boolean> bolusCheckSubject = BehaviorSubject.create();
private final BehaviorSubject<Boolean> exbolusCheckSubject = BehaviorSubject.create();
private final 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(null);
SystemClock.sleep(650);
}
bolusCheckSubject.onNext(true);
if (pumpSync.expectedPumpState().getExtendedBolus() != null) {
uel.log(UserEntryMapper.Action.CANCEL_EXTENDED_BOLUS, UserEntryMapper.Sources.EOPatch2);
commandQueue.cancelExtended(new Callback() {
@Override
public void run() {
exbolusCheckSubject.onNext(true);
}
});
}else{
exbolusCheckSubject.onNext(true);
}
if (pumpSync.expectedPumpState().getTemporaryBasal() != null) {
uel.log(UserEntryMapper.Action.CANCEL_TEMP_BASAL, UserEntryMapper.Sources.EOPatch2);
commandQueue.cancelTempBasal(true, new Callback() {
@Override
public void run() {
basalCheckSubject.onNext(true);
}
});
}else{
basalCheckSubject.onNext(true);
}
return Observable.zip(getBolusSebject(), getExbolusSebject(), getBasalSebject(), (bolusReady, exbolusReady, basalReady)
-> (bolusReady && exbolusReady && basalReady))
.filter(ready -> ready)
.flatMap(v -> isReady())
.concatMapSingle(v -> BASAL_STOP.stop())
.doOnNext(this::checkResponse)
.firstOrError()
.doOnSuccess(this::onBasalStopped)
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StopBasalTask error"));
}
private void onBasalStopped(BasalStopResponse response) {
enqueue(TaskFunc.UPDATE_CONNECTION);
}
public synchronized void enqueue() {
boolean ready = (disposable == null || disposable.isDisposed());
if (ready) {
disposable = stop()
.timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
.subscribe(v -> {
bolusCheckSubject.onNext(false);
exbolusCheckSubject.onNext(false);
basalCheckSubject.onNext(false);
});
}
}
@Override
protected void preCondition() throws Exception {
checkPatchConnected();
}
}

View file

@ -0,0 +1,84 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.pump.eopatch.core.define.IPatchConstant;
import info.nightscout.androidaps.plugins.pump.eopatch.core.code.PatchBleResultCode;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BolusStop;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusStopResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.ComboBolusStopResponse;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class StopComboBolusTask extends BolusTask {
private final BolusStop BOLUS_STOP;
@Inject
public StopComboBolusTask() {
super(TaskFunc.STOP_COMBO_BOLUS);
BOLUS_STOP = new BolusStop();
}
public Single<ComboBolusStopResponse> stop() {
return isReady()
.concatMapSingle(v -> stopJob())
.firstOrError()
.doOnSuccess(this::checkResponse)
.doOnSuccess(this::onComboBolusStopped)
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StopComboBolusTask error"));
}
public Single<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,57 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.pump.eopatch.core.define.IPatchConstant;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BolusStop;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusStopResponse;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class StopExtBolusTask extends BolusTask {
private final BolusStop BOLUS_STOP;
@Inject
public StopExtBolusTask() {
super(TaskFunc.STOP_EXT_BOLUS);
BOLUS_STOP = new BolusStop();
}
public Single<BolusStopResponse> stop() {
return isReady().concatMapSingle(v -> stopJob()).firstOrError()
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StopExtBolusTask error"));
}
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,59 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BolusStop;
import info.nightscout.androidaps.plugins.pump.eopatch.core.define.IPatchConstant;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BolusStopResponse;
import info.nightscout.androidaps.utils.rx.AapsSchedulers;
import info.nightscout.shared.logging.LTag;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class StopNowBolusTask extends BolusTask {
private final BolusStop BOLUS_STOP;
@Inject AapsSchedulers aapsSchedulers;
@Inject
public StopNowBolusTask() {
super(TaskFunc.STOP_NOW_BOLUS);
BOLUS_STOP = new BolusStop();
}
public Single<BolusStopResponse> stop() {
return isReady()
.observeOn(aapsSchedulers.getMain())
.concatMapSingle(v -> stopJob()).firstOrError()
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StopNowBolusTask error"));
}
public Single<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,55 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import info.nightscout.shared.logging.LTag;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.TempBasalScheduleStop;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.PatchBooleanResponse;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class StopTempBasalTask extends TaskBase {
private final TempBasalScheduleStop TEMP_BASAL_SCHEDULE_STOP;
@Inject
public StopTempBasalTask() {
super(TaskFunc.STOP_TEMP_BASAL);
TEMP_BASAL_SCHEDULE_STOP = new TempBasalScheduleStop();
}
public Single<PatchBooleanResponse> stop() {
return isReady().concatMapSingle(v -> stopJob()).firstOrError()
.doOnError(e -> aapsLogger.error(LTag.PUMPCOMM, (e.getMessage() != null) ? e.getMessage() : "StopTempBasalTask error"));
}
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,139 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BasalHistoryGetExBig;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.BasalHistoryIndexGet;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.TempBasalHistoryGetExBig;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BasalHistoryIndexResponse;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.BasalHistoryResponse;
import io.reactivex.rxjava3.core.Single;
import static info.nightscout.androidaps.plugins.pump.eopatch.core.define.IPatchConstant.BASAL_HISTORY_SIZE_BIG;
@Singleton
public class SyncBasalHistoryTask extends TaskBase {
@Inject IPreferenceManager pm;
private final BasalHistoryIndexGet BASAL_HISTORY_INDEX_GET;
private final BasalHistoryGetExBig BASAL_HISTORY_GET_EX_BIG;
private final TempBasalHistoryGetExBig TEMP_BASAL_HISTORY_GET_EX_BIG;
@Inject
public SyncBasalHistoryTask() {
super(TaskFunc.SYNC_BASAL_HISTORY);
BASAL_HISTORY_INDEX_GET = new BasalHistoryIndexGet();
BASAL_HISTORY_GET_EX_BIG = new BasalHistoryGetExBig();
TEMP_BASAL_HISTORY_GET_EX_BIG = new TempBasalHistoryGetExBig();
}
public Single<Integer> sync(int end) {
return Single.just(1); // 베이젤 싱크 사용 안함
}
public Single<Integer> sync() {
return Single.just(1); // 베이젤 싱크 사용 안함
}
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) {
if (!n.isSuccess() || !t.isSuccess() || n.getSeq() != t.getSeq()) {
return -1;
}
int start = n.getSeq();
float[] normal = n.getInjectedDoseValues();
float[] temp = t.getInjectedDoseValues();
int count = Math.min(end - start + 1, BASAL_HISTORY_SIZE_BIG);
count = Math.min(count, normal.length);
count = Math.min(count, temp.length);
return updateInjected(normal, temp, start, end);
}
public synchronized int updateInjected(float[] normal, float[] temp, int start, int end) {
if (pm.getPatchState().isPatchInternalSuspended() && pm.getPatchConfig().isInBasalPausedTime() == false) {
return -1;
}
int lastUpdatedIndex = -1;
int count = end - start + 1;
if (count > normal.length) {
count = normal.length;
}
if (count > 0) {
int lastSyncIndex = pm.getPatchConfig().getLastIndex();
for (int i = 0;i < count;i++) {
int seq = start + i;
if (seq < lastSyncIndex)
continue;
if (start <= seq && seq <= end) {
lastUpdatedIndex = seq;
}
}
}
return lastUpdatedIndex;
}
private void updatePatchLastIndex(int newIndex) {
int lastIndex = pm.getPatchConfig().getLastIndex();
if (lastIndex < newIndex) {
pm.getPatchConfig().setLastIndex(newIndex);
pm.flushPatchConfig();
}
}
}

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.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.Disposable;
@Singleton
public class TaskBase {
protected IBleDevice patch;
@Inject AAPSLogger aapsLogger;
@Inject protected IPreferenceManager pm;
@Inject TaskQueue taskQueue;
TaskFunc func;
static HashMap<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,114 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ble.task;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.utils.rx.AapsSchedulers;
import info.nightscout.shared.logging.AAPSLogger;
import info.nightscout.shared.logging.LTag;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
@Singleton
public class TaskQueue {
@Inject AAPSLogger aapsLogger;
@Inject AapsSchedulers aapsSchedulers;
Queue<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 -> ticketSubject
.takeUntil(it -> it.number > v)
.filter(it -> it.number == v))
.doOnNext(v -> aapsLogger.debug(LTag.PUMPCOMM, String.format("Task #:%s started func:%s", v.number, v.func.name())))
.observeOn(aapsSchedulers.getIo())
.map(it -> it.func)
.doFinally(this::done);
}
protected synchronized Observable<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> iterator = queue.iterator();
/* remove 1st queue */
iterator.next();
while (iterator.hasNext()) {
PatchTask item = iterator.next();
if (item.func == func) {
return true;
}
}
}
return false;
}
static class PatchTask {
int number;
TaskFunc func;
PatchTask(int number, TaskFunc func) {
this.number = number;
this.func = func;
}
}
}

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.vo.PatchState;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.plugins.pump.eopatch.core.api.UpdateConnection;
import info.nightscout.androidaps.plugins.pump.eopatch.core.response.UpdateConnectionResponse;
import io.reactivex.rxjava3.core.Single;
@Singleton
public class UpdateConnectionTask extends TaskBase {
@Inject PatchStateManager patchStateManager;
private UpdateConnection UPDATE_CONNECTION;
@Inject
public UpdateConnectionTask() {
super(TaskFunc.UPDATE_CONNECTION);
UPDATE_CONNECTION = new UpdateConnection();
}
public Single<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() != null) ? e.getMessage() : "UpdateConnectionTask error"));
}
private void onUpdateConnection(PatchState patchState) {
patchStateManager.updatePatchState(patchState);
}
public synchronized void enqueue() {
boolean ready = (disposable == null || disposable.isDisposed());
if (ready) {
disposable = update()
.timeout(TASK_ENQUEUE_TIME_OUT, TimeUnit.SECONDS)
.subscribe();
}
}
}

View file

@ -0,0 +1,7 @@
package info.nightscout.androidaps.plugins.pump.eopatch.code
enum class AlarmCategory{
NONE,
ALARM,
ALERT;
}

View file

@ -0,0 +1,34 @@
package info.nightscout.androidaps.plugins.pump.eopatch.code
enum class BasalStatus constructor(val rawValue: Int) {
STOPPED(0),
PAUSED(1), //템프베이젤 주입중
SUSPENDED(2), //주입 정지
STARTED(3), //주입중
SELECTED(4); //패치 폐기
val isStarted: Boolean
get() = this == STARTED
val isSuspended: Boolean
get() = this == SUSPENDED
val isStopped: Boolean
get() = this == STOPPED
companion object {
@JvmStatic
fun ofRaw(rawValue: Int?): BasalStatus {
if (rawValue == null) {
return STOPPED
}
for (t in values()) {
if (t.rawValue == rawValue) {
return t
}
}
return STOPPED
}
}
}

View file

@ -0,0 +1,40 @@
package info.nightscout.androidaps.plugins.pump.eopatch.code
import java.util.concurrent.TimeUnit
enum class BolusExDuration constructor(val index: Int, val minute: Int, val hour: Float) {
OFF(0, 0, 0f),
MINUTE_30(1, 30, 0.5f),
MINUTE_60(2, 60, 1.0f),
MINUTE_90(3, 90, 1.5f),
MINUTE_120(4, 120, 2.0f),
MINUTE_150(5, 150, 2.5f),
MINUTE_180(6, 180, 3.0f),
MINUTE_210(7, 210, 3.5f),
MINUTE_240(8, 240, 4.0f),
MINUTE_270(9, 270, 4.5f),
MINUTE_300(10, 300, 5.0f),
MINUTE_330(11, 330, 5.5f),
MINUTE_360(12, 360, 6.0f),
MINUTE_390(13, 390, 6.5f),
MINUTE_420(14, 420, 7.0f),
MINUTE_450(15, 450, 7.5f),
MINUTE_480(16, 480, 8.0f);
fun milli(): Long {
return TimeUnit.MINUTES.toMillis(this.minute.toLong())
}
companion object {
@JvmStatic
fun ofRaw(rawValue: Int): BolusExDuration {
for (t in values()) {
if (t.minute == rawValue) {
return t
}
}
return OFF
}
}
}

View file

@ -0,0 +1,22 @@
package info.nightscout.androidaps.plugins.pump.eopatch.code
enum class DeactivationStatus {
DEACTIVATION_FAILED,
NORMAL_DEACTIVATED,
FORCE_DEACTIVATED;
val isDeactivated: Boolean
get() = this == NORMAL_DEACTIVATED || this == FORCE_DEACTIVATED
companion object {
@JvmStatic
fun of(isSuccess: Boolean, forced: Boolean): DeactivationStatus {
return when {
isSuccess -> NORMAL_DEACTIVATED
forced -> FORCE_DEACTIVATED
else -> DEACTIVATION_FAILED
}
}
}
}

View file

@ -0,0 +1,22 @@
package info.nightscout.androidaps.plugins.pump.eopatch.code
enum class EventType {
ACTIVATION_CLICKED,
DEACTIVATION_CLICKED,
SUSPEND_CLICKED,
RESUME_CLICKED,
INVALID_BASAL_RATE,
PROFILE_NOT_SET,
SHOW_PATCH_COMM_DIALOG,
DISMISS_PATCH_COMM_DIALOG,
SHOW_PATCH_COMM_ERROR_DIALOG,
SHOW_BONDED_DIALOG,
SHOW_CHANGE_PATCH_DIALOG,
FINISH_ACTIVITY,
SHOW_DISCARD_DIALOG,
PAUSE_BASAL_SUCCESS,
PAUSE_BASAL_FAILED,
RESUME_BASAL_SUCCESS,
RESUME_BASAL_FAILED
;
}

View file

@ -0,0 +1,28 @@
package info.nightscout.androidaps.plugins.pump.eopatch.code
enum class PatchExpireAlertTime constructor(val index: Int, val hour: Int) {
HOUR_1(0, 1),
HOUR_2(1, 2),
HOUR_3(2, 3),
HOUR_4(3, 4),
HOUR_5(4, 5),
HOUR_6(5, 6),
HOUR_7(6, 7),
HOUR_8(7, 8),
HOUR_9(8, 9),
HOUR_10(9, 10),
HOUR_11(10, 11),
HOUR_12(11, 12),
HOUR_13(12, 13),
HOUR_14(13, 14),
HOUR_15(14, 15),
HOUR_16(15, 16),
HOUR_17(16, 17),
HOUR_18(17, 18),
HOUR_19(18, 19),
HOUR_20(19, 20),
HOUR_21(20, 21),
HOUR_22(21, 22),
HOUR_23(22, 23),
HOUR_24(23, 24);
}

View file

@ -0,0 +1,19 @@
package info.nightscout.androidaps.plugins.pump.eopatch.code
enum class PatchLifecycle constructor(val rawValue: Int) {
SHUTDOWN(1),
BONDED(2),
SAFETY_CHECK(3),
REMOVE_NEEDLE_CAP(4),
REMOVE_PROTECTION_TAPE(5),
ROTATE_KNOB(6),
BASAL_SETTING(7),
ACTIVATED(8);
val isShutdown: Boolean
get() = this == SHUTDOWN
val isActivated: Boolean
get() = this == ACTIVATED
}

View file

@ -0,0 +1,29 @@
package info.nightscout.androidaps.plugins.pump.eopatch.code
enum class PatchStep {
SAFE_DEACTIVATION,
MANUALLY_TURNING_OFF_ALARM,
DISCARDED,
DISCARDED_FOR_CHANGE,
DISCARDED_FROM_ALARM,
WAKE_UP,
CONNECT_NEW,
REMOVE_NEEDLE_CAP,
REMOVE_PROTECTION_TAPE,
SAFETY_CHECK,
ROTATE_KNOB,
ROTATE_KNOB_NEEDLE_INSERTION_ERROR,
BASAL_SCHEDULE,
SETTING_REMINDER_TIME,
CHECK_CONNECTION,
CANCEL,
COMPLETE,
BACK_TO_HOME,
FINISH;
val isSafeDeactivation: Boolean
get() = this == SAFE_DEACTIVATION
val isCheckConnection: Boolean
get() = this == CHECK_CONNECTION
}

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_RESERVOIR_REMINDERS: Int = R.string.key_eopatch_low_reservoir_reminders
val EXPIRATION_REMINDERS: Int = R.string.key_eopatch_expiration_reminders
val BUZZER_REMINDERS: Int = R.string.key_eopatch_patch_buzzer_reminders
val PATCH_CONFIG: Int = R.string.key_eopatch_patch_config
val PATCH_STATE: Int = R.string.key_eopatch_patch_state
val BOLUS_CURRENT: Int = R.string.key_eopatch_bolus_current
val NORMAL_BASAL: Int = R.string.key_eopatch_normal_basal
val TEMP_BASAL: Int = R.string.key_eopatch_temp_basal
val ALARMS: Int = R.string.key_eopatch_bolus_current
}
}

View file

@ -0,0 +1,8 @@
package info.nightscout.androidaps.plugins.pump.eopatch.code
enum class UnitOrPercent {
P,
U;
fun isPercentage() = this == P
}

View file

@ -0,0 +1,12 @@
package info.nightscout.androidaps.plugins.pump.eopatch.dagger
import javax.inject.Qualifier
import javax.inject.Scope
@Qualifier
annotation class EopatchPluginQualifier
@MustBeDocumented
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class FragmentScope

View file

@ -0,0 +1,139 @@
package info.nightscout.androidaps.plugins.pump.eopatch.dagger
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.android.ContributesAndroidInjector
import dagger.multibindings.IntoMap
import info.nightscout.androidaps.plugins.pump.eopatch.OsAlarmReceiver
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmManager
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.AlarmRegistry
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmManager
import info.nightscout.androidaps.plugins.pump.eopatch.alarm.IAlarmRegistry
import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPatchManager
import info.nightscout.androidaps.plugins.pump.eopatch.ble.IPreferenceManager
import info.nightscout.androidaps.plugins.pump.eopatch.ble.PatchManager
import info.nightscout.androidaps.plugins.pump.eopatch.ble.PreferenceManager
import info.nightscout.androidaps.plugins.pump.eopatch.ui.*
import info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs.AlarmDialog
import info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs.ActivationNotCompleteDialog
import info.nightscout.androidaps.plugins.pump.eopatch.ui.dialogs.CommonDialog
import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchOverviewViewModel
import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.EopatchViewModel
import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.ViewModelFactory
import info.nightscout.androidaps.plugins.pump.eopatch.ui.viewmodel.ViewModelKey
import javax.inject.Provider
import javax.inject.Singleton
@Module(includes = [EopatchPrefModule::class])
@Suppress("unused")
abstract class EopatchModule {
companion object {
@Provides
@EopatchPluginQualifier
fun providesViewModelFactory(@EopatchPluginQualifier viewModels: MutableMap<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,38 @@
package info.nightscout.androidaps.plugins.pump.eopatch.dagger
import dagger.Module
import dagger.Provides
import info.nightscout.androidaps.plugins.pump.eopatch.vo.Alarms
import info.nightscout.androidaps.plugins.pump.eopatch.vo.NormalBasalManager
import info.nightscout.androidaps.plugins.pump.eopatch.vo.PatchConfig
import info.nightscout.androidaps.plugins.pump.eopatch.vo.TempBasalManager
import javax.inject.Singleton
@Module
class EopatchPrefModule {
@Provides
@Singleton
internal fun providePatchConfig(): PatchConfig {
return PatchConfig()
}
@Provides
@Singleton
internal fun provideNormalBasalManager(): NormalBasalManager {
return NormalBasalManager()
}
@Provides
@Singleton
internal fun provideTempBasalManager(): TempBasalManager {
return TempBasalManager()
}
@Provides
@Singleton
internal fun provideAlarms(): Alarms {
return Alarms()
}
}

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,19 @@
package info.nightscout.androidaps.plugins.pump.eopatch.extension
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
fun AppCompatActivity.replaceFragmentInActivity(fragment: Fragment, frameId: Int, addToBackStack: Boolean = false) {
supportFragmentManager.transact {
replace(frameId, fragment)
if (addToBackStack) addToBackStack(null)
}
}
private inline fun FragmentManager.transact(action: FragmentTransaction.() -> Unit) {
beginTransaction().apply {
action()
}.commit()
}

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