commit
ac333f6968
76
_docs/icons/ic_medtrum_128.svg
Normal file
76
_docs/icons/ic_medtrum_128.svg
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="128px" height="128px" viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
|
||||||
|
<g id="Layer_7">
|
||||||
|
<g id="Medtrum_2">
|
||||||
|
<path fill="#FFFFFF" d="M94.383,15.26H34.367c-16.327,0-29.563,13.3-29.563,29.706v38.687c0,16.406,13.236,29.706,29.563,29.706
|
||||||
|
h60.017c16.327,0,29.563-13.3,29.563-29.706V44.965C123.946,28.559,110.71,15.26,94.383,15.26z M56.935,106.393
|
||||||
|
c-1.272,0-2.303-1.031-2.303-2.303c0-1.272,1.031-2.303,2.303-2.303c1.272,0,2.303,1.031,2.303,2.303
|
||||||
|
C59.238,105.362,58.207,106.393,56.935,106.393z M56.935,26.83c-1.272,0-2.303-1.031-2.303-2.303c0-1.272,1.031-2.303,2.303-2.303
|
||||||
|
c1.272,0,2.303,1.031,2.303,2.303C59.238,25.801,58.207,26.83,56.935,26.83z M112.167,73.321V55.296
|
||||||
|
c2.428,2.241,3.95,5.448,3.95,9.013S114.595,71.081,112.167,73.321z"/>
|
||||||
|
<g>
|
||||||
|
<path fill="#E0E0E0" d="M88.268,99.547c5.196-0.229,9.454-0.602,11.926-1.147c8.841-1.951,12.243-6.224,12.243-13.385v-1.263
|
||||||
|
V44.865v-1.263c0-7.162-3.402-11.436-12.243-13.385c-2.472-0.546-6.73-0.918-11.926-1.147"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#AAAAAA" d="M88.531,29.079c-19.865-0.79-52.154,0.131-58.579,1.565c-8.285,1.848-12.095,5.194-13.418,12.916
|
||||||
|
c-0.606,3.544-0.627,12.306-0.473,20.748c-0.154,8.442-0.134,17.204,0.473,20.75c1.323,7.721,5.133,11.066,13.418,12.916
|
||||||
|
c6.425,1.434,38.715,2.355,58.579,1.565"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#EAEAEA" d="M88.268,98.819c5.011-0.224,9.117-0.59,11.502-1.123c8.527-1.911,11.808-6.095,11.808-13.109v-1.237
|
||||||
|
V45.267V44.03c0-7.014-3.281-11.199-11.808-13.109c-2.384-0.535-6.491-0.899-11.502-1.123"/>
|
||||||
|
</g>
|
||||||
|
<path fill="#B2B2B2" d="M88.531,29.807c-19.567-0.774-51.374,0.128-57.702,1.533c-8.161,1.81-11.914,5.087-13.217,12.649
|
||||||
|
c-0.597,3.471-0.618,12.052-0.466,20.32c-0.152,8.268-0.132,16.849,0.466,20.321c1.303,7.562,5.056,10.837,13.217,12.649
|
||||||
|
c6.329,1.405,38.135,2.306,57.702,1.533"/>
|
||||||
|
<g>
|
||||||
|
<path fill="#F0F0F0" d="M88.268,97.88c4.751-0.218,8.643-0.573,10.904-1.092c8.084-1.859,11.194-5.929,11.194-12.752v-1.203
|
||||||
|
V45.785v-1.203c0-6.823-3.11-10.895-11.194-12.752c-2.261-0.52-6.153-0.874-10.904-1.092"/>
|
||||||
|
</g>
|
||||||
|
<path fill="#B9B9B9" d="M88.531,30.745c-19.148-0.753-50.274,0.124-56.467,1.491c-7.986,1.761-11.659,4.949-12.934,12.305
|
||||||
|
c-0.585,3.377-0.604,11.724-0.456,19.767c-0.149,8.043-0.129,16.39,0.456,19.768c1.275,7.356,4.948,10.542,12.934,12.305
|
||||||
|
c6.193,1.367,37.319,2.244,56.467,1.491"/>
|
||||||
|
<g>
|
||||||
|
<path fill="#F4F4F4" d="M88.268,96.224c4.241-0.207,7.717-0.545,9.735-1.039c7.217-1.767,9.994-5.637,9.994-12.123v-1.144V46.698
|
||||||
|
v-1.144c0-6.486-2.777-10.357-9.994-12.123c-2.018-0.495-5.494-0.831-9.735-1.039"/>
|
||||||
|
</g>
|
||||||
|
<path fill="#BEBEBE" d="M88.531,32.401c-18.728-0.715-49.17,0.118-55.227,1.417c-7.811,1.674-11.403,4.705-12.65,11.698
|
||||||
|
c-0.572,3.21-0.591,11.146-0.446,18.792c-0.145,7.646-0.126,15.582,0.446,18.793c1.247,6.993,4.839,10.022,12.65,11.698
|
||||||
|
c6.057,1.299,36.499,2.133,55.227,1.417"/>
|
||||||
|
<g>
|
||||||
|
|
||||||
|
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="157.3581" y1="-156.2687" x2="184.8192" y2="-156.2687" gradientTransform="matrix(1 0 0 -1 -97.66 -91.96)">
|
||||||
|
<stop offset="0" style="stop-color:#BBBDBF"/>
|
||||||
|
<stop offset="0.1232" style="stop-color:#B5B7B9"/>
|
||||||
|
<stop offset="0.5123" style="stop-color:#B5B7B9"/>
|
||||||
|
<stop offset="0.5764" style="stop-color:#B5B7B9"/>
|
||||||
|
<stop offset="1" style="stop-color:#929497"/>
|
||||||
|
</linearGradient>
|
||||||
|
<polyline fill="url(#SVGID_1_)" stroke="#9D9DA0" stroke-width="0.4252" stroke-miterlimit="10" points="59.698,75.534
|
||||||
|
76.854,75.534 76.854,74.24 87.159,74.24 87.159,54.379 76.854,54.379 76.854,53.083 59.698,53.083 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#D0D2D3" stroke="#A3A3A3" stroke-width="0.4252" stroke-miterlimit="10" d="M90.39,68.37
|
||||||
|
c0,1.835-1.432,3.322-3.198,3.322H72.747c-1.766,0-3.198-1.487-3.198-3.322v-8.12c0-1.835,1.432-3.322,3.198-3.322h14.446
|
||||||
|
c1.766,0,3.198,1.487,3.198,3.322V68.37z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#F4F4F4" stroke="#C1C1C1" stroke-width="0.2835" stroke-miterlimit="10" d="M84.165,74.54
|
||||||
|
c0,4.387-0.05,4.448,3.627,4.448l0,0c4.387,0,7.945-3.557,7.945-7.945v-13.47c0-4.387-3.557-7.945-7.945-7.945l0,0
|
||||||
|
c-3.678,0-3.627,0.102-3.627,4.489V74.54z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path fill="#F1F1F2" stroke="#B2B2B2" stroke-width="0.2835" stroke-miterlimit="10" d="M94.571,67.677
|
||||||
|
c0,0.834-0.677,1.511-1.511,1.511l0,0c-0.834,0-1.511-0.677-1.511-1.511v-6.735c0-0.834,0.677-1.511,1.511-1.511l0,0
|
||||||
|
c0.834,0,1.511,0.677,1.511,1.511V67.677z"/>
|
||||||
|
</g>
|
||||||
|
<path fill="#231F20" d="M94.291,61.74c0,0.679-0.551,1.231-1.231,1.231l0,0c-0.679,0-1.231-0.551-1.231-1.231l0,0
|
||||||
|
c0-0.679,0.551-1.231,1.231-1.231l0,0C93.74,60.51,94.291,61.06,94.291,61.74L94.291,61.74z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.8 KiB |
|
@ -213,6 +213,7 @@ dependencies {
|
||||||
implementation project(':pump:danar')
|
implementation project(':pump:danar')
|
||||||
implementation project(':pump:diaconn')
|
implementation project(':pump:diaconn')
|
||||||
implementation project(':pump:eopatch')
|
implementation project(':pump:eopatch')
|
||||||
|
implementation project(':pump:medtrum')
|
||||||
implementation project(':insight')
|
implementation project(':insight')
|
||||||
implementation project(':pump:medtronic')
|
implementation project(':pump:medtronic')
|
||||||
implementation project(':pump:pump-common')
|
implementation project(':pump:pump-common')
|
||||||
|
|
|
@ -54,6 +54,7 @@ import info.nightscout.plugins.sync.xdrip.XdripPlugin
|
||||||
import info.nightscout.pump.combo.ComboPlugin
|
import info.nightscout.pump.combo.ComboPlugin
|
||||||
import info.nightscout.pump.combov2.ComboV2Plugin
|
import info.nightscout.pump.combov2.ComboV2Plugin
|
||||||
import info.nightscout.pump.diaconn.DiaconnG8Plugin
|
import info.nightscout.pump.diaconn.DiaconnG8Plugin
|
||||||
|
import info.nightscout.pump.medtrum.MedtrumPlugin
|
||||||
import info.nightscout.pump.virtual.VirtualPumpPlugin
|
import info.nightscout.pump.virtual.VirtualPumpPlugin
|
||||||
import info.nightscout.rx.bus.RxBus
|
import info.nightscout.rx.bus.RxBus
|
||||||
import info.nightscout.rx.events.EventPreferenceChange
|
import info.nightscout.rx.events.EventPreferenceChange
|
||||||
|
@ -122,6 +123,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
|
||||||
@Inject lateinit var wearPlugin: WearPlugin
|
@Inject lateinit var wearPlugin: WearPlugin
|
||||||
@Inject lateinit var maintenancePlugin: MaintenancePlugin
|
@Inject lateinit var maintenancePlugin: MaintenancePlugin
|
||||||
@Inject lateinit var eopatchPumpPlugin: EopatchPumpPlugin
|
@Inject lateinit var eopatchPumpPlugin: EopatchPumpPlugin
|
||||||
|
@Inject lateinit var medtrumPlugin: MedtrumPlugin
|
||||||
|
|
||||||
@Inject lateinit var passwordCheck: PasswordCheck
|
@Inject lateinit var passwordCheck: PasswordCheck
|
||||||
@Inject lateinit var nsSettingStatus: NSSettingsStatus
|
@Inject lateinit var nsSettingStatus: NSSettingsStatus
|
||||||
|
@ -212,6 +214,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
|
||||||
addPreferencesFromResourceIfEnabled(medtronicPumpPlugin, rootKey, config.PUMPDRIVERS)
|
addPreferencesFromResourceIfEnabled(medtronicPumpPlugin, rootKey, config.PUMPDRIVERS)
|
||||||
addPreferencesFromResourceIfEnabled(diaconnG8Plugin, rootKey, config.PUMPDRIVERS)
|
addPreferencesFromResourceIfEnabled(diaconnG8Plugin, rootKey, config.PUMPDRIVERS)
|
||||||
addPreferencesFromResourceIfEnabled(eopatchPumpPlugin, rootKey, config.PUMPDRIVERS)
|
addPreferencesFromResourceIfEnabled(eopatchPumpPlugin, rootKey, config.PUMPDRIVERS)
|
||||||
|
addPreferencesFromResourceIfEnabled(medtrumPlugin, rootKey, config.PUMPDRIVERS)
|
||||||
addPreferencesFromResource(R.xml.pref_pump, rootKey, config.PUMPDRIVERS)
|
addPreferencesFromResource(R.xml.pref_pump, rootKey, config.PUMPDRIVERS)
|
||||||
addPreferencesFromResourceIfEnabled(virtualPumpPlugin, rootKey)
|
addPreferencesFromResourceIfEnabled(virtualPumpPlugin, rootKey)
|
||||||
addPreferencesFromResourceIfEnabled(insulinOrefFreePeakPlugin, rootKey)
|
addPreferencesFromResourceIfEnabled(insulinOrefFreePeakPlugin, rootKey)
|
||||||
|
|
|
@ -31,6 +31,7 @@ import info.nightscout.pump.common.di.PumpCommonModule
|
||||||
import info.nightscout.pump.dana.di.DanaHistoryModule
|
import info.nightscout.pump.dana.di.DanaHistoryModule
|
||||||
import info.nightscout.pump.dana.di.DanaModule
|
import info.nightscout.pump.dana.di.DanaModule
|
||||||
import info.nightscout.pump.danars.di.DanaRSModule
|
import info.nightscout.pump.danars.di.DanaRSModule
|
||||||
|
import info.nightscout.pump.medtrum.di.MedtrumModule
|
||||||
import info.nightscout.pump.diaconn.di.DiaconnG8Module
|
import info.nightscout.pump.diaconn.di.DiaconnG8Module
|
||||||
import info.nightscout.pump.virtual.di.VirtualPumpModule
|
import info.nightscout.pump.virtual.di.VirtualPumpModule
|
||||||
import info.nightscout.rx.di.RxModule
|
import info.nightscout.rx.di.RxModule
|
||||||
|
@ -87,6 +88,7 @@ import javax.inject.Singleton
|
||||||
OmnipodErosModule::class,
|
OmnipodErosModule::class,
|
||||||
PumpCommonModule::class,
|
PumpCommonModule::class,
|
||||||
RileyLinkModule::class,
|
RileyLinkModule::class,
|
||||||
|
MedtrumModule::class,
|
||||||
VirtualPumpModule::class
|
VirtualPumpModule::class
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -46,6 +46,7 @@ import info.nightscout.plugins.sync.tidepool.TidepoolPlugin
|
||||||
import info.nightscout.plugins.sync.xdrip.XdripPlugin
|
import info.nightscout.plugins.sync.xdrip.XdripPlugin
|
||||||
import info.nightscout.pump.combo.ComboPlugin
|
import info.nightscout.pump.combo.ComboPlugin
|
||||||
import info.nightscout.pump.combov2.ComboV2Plugin
|
import info.nightscout.pump.combov2.ComboV2Plugin
|
||||||
|
import info.nightscout.pump.medtrum.MedtrumPlugin
|
||||||
import info.nightscout.pump.diaconn.DiaconnG8Plugin
|
import info.nightscout.pump.diaconn.DiaconnG8Plugin
|
||||||
import info.nightscout.pump.virtual.VirtualPumpPlugin
|
import info.nightscout.pump.virtual.VirtualPumpPlugin
|
||||||
import info.nightscout.sensitivity.SensitivityAAPSPlugin
|
import info.nightscout.sensitivity.SensitivityAAPSPlugin
|
||||||
|
@ -209,6 +210,12 @@ abstract class PluginsListModule {
|
||||||
@IntKey(156)
|
@IntKey(156)
|
||||||
abstract fun bindEopatchPumpPlugin(plugin: EopatchPumpPlugin): PluginBase
|
abstract fun bindEopatchPumpPlugin(plugin: EopatchPumpPlugin): PluginBase
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@PumpDriver
|
||||||
|
@IntoMap
|
||||||
|
@IntKey(160)
|
||||||
|
abstract fun bindMedtrumPlugin(plugin: MedtrumPlugin): PluginBase
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@AllConfigs
|
@AllConfigs
|
||||||
@IntoMap
|
@IntoMap
|
||||||
|
|
|
@ -131,9 +131,12 @@ open class Notification {
|
||||||
const val IDENTIFICATION_NOT_SET = 77
|
const val IDENTIFICATION_NOT_SET = 77
|
||||||
const val PERMISSION_BT = 78
|
const val PERMISSION_BT = 78
|
||||||
const val EOELOW_PATCH_ALERTS = 79
|
const val EOELOW_PATCH_ALERTS = 79
|
||||||
const val COMBO_PUMP_SUSPENDED = 80
|
const val PUMP_SUSPENDED = 80
|
||||||
const val COMBO_UNKNOWN_TBR = 81
|
const val COMBO_UNKNOWN_TBR = 81
|
||||||
const val BLUETOOTH_NOT_ENABLED = 82
|
const val BLUETOOTH_NOT_ENABLED = 82
|
||||||
|
const val PATCH_NOT_ACTIVE = 83
|
||||||
|
const val PUMP_SETTINGS_FAILED = 84
|
||||||
|
const val PUMP_TIMEZONE_UPDATE_FAILED = 85
|
||||||
|
|
||||||
const val USER_MESSAGE = 1000
|
const val USER_MESSAGE = 1000
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package info.nightscout.interfaces.pump
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functionality supported by Medtrum* pumps only
|
||||||
|
*/
|
||||||
|
interface Medtrum {
|
||||||
|
|
||||||
|
fun loadEvents(): PumpEnactResult // events history to build treatments from
|
||||||
|
fun setUserOptions(): PumpEnactResult // set user settings
|
||||||
|
fun clearAlarms(): PumpEnactResult // clear alarms
|
||||||
|
fun deactivate(): PumpEnactResult // deactivate patch
|
||||||
|
fun updateTime(): PumpEnactResult // update time
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package info.nightscout.interfaces.pump.defs
|
||||||
|
|
||||||
enum class ManufacturerType(val description: String) {
|
enum class ManufacturerType(val description: String) {
|
||||||
AAPS("AAPS"),
|
AAPS("AAPS"),
|
||||||
|
Medtrum("Medtrum"),
|
||||||
Medtronic("Medtronic"),
|
Medtronic("Medtronic"),
|
||||||
Sooil("SOOIL"),
|
Sooil("SOOIL"),
|
||||||
Tandem("Tandem"),
|
Tandem("Tandem"),
|
||||||
|
|
|
@ -26,6 +26,7 @@ enum class PumpCapability {
|
||||||
YpsomedCapabilities(arrayOf(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, ReplaceBattery, TDD, ManualTDDLoad)), // BasalRates (separately grouped)
|
YpsomedCapabilities(arrayOf(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, ReplaceBattery, TDD, ManualTDDLoad)), // BasalRates (separately grouped)
|
||||||
DiaconnCapabilities(arrayOf(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, ReplaceBattery, TDD, ManualTDDLoad)), //
|
DiaconnCapabilities(arrayOf(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, ReplaceBattery, TDD, ManualTDDLoad)), //
|
||||||
EopatchCapabilities(arrayOf(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, BasalRate30min)),
|
EopatchCapabilities(arrayOf(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, BasalRate30min)),
|
||||||
|
MedtrumCapabilities(arrayOf(Bolus, TempBasal, BasalProfileSet, BasalRate30min, TDD)), // Technically the pump supports ExtendedBolus, but not implemented (yet)
|
||||||
BasalRate_Duration15minAllowed,
|
BasalRate_Duration15minAllowed,
|
||||||
BasalRate_Duration30minAllowed,
|
BasalRate_Duration30minAllowed,
|
||||||
BasalRate_Duration15and30minAllowed(arrayOf(BasalRate_Duration15minAllowed, BasalRate_Duration30minAllowed)),
|
BasalRate_Duration15and30minAllowed(arrayOf(BasalRate_Duration15minAllowed, BasalRate_Duration30minAllowed)),
|
||||||
|
|
|
@ -391,7 +391,29 @@ enum class PumpType {
|
||||||
isPatchPump = true,
|
isPatchPump = true,
|
||||||
maxReservoirReading = 50,
|
maxReservoirReading = 50,
|
||||||
source = Source.EOPatch2
|
source = Source.EOPatch2
|
||||||
);
|
),
|
||||||
|
|
||||||
|
//Medtrum Nano Pump
|
||||||
|
MEDTRUM_NANO(
|
||||||
|
description = "Medtrum Nano",
|
||||||
|
manufacturer = ManufacturerType.Medtrum,
|
||||||
|
model = "Nano",
|
||||||
|
bolusSize = 0.05,
|
||||||
|
specialBolusSize = null,
|
||||||
|
extendedBolusSettings = DoseSettings(0.05, 30, 8 * 60, 0.05, 30.0),
|
||||||
|
pumpTempBasalType = PumpTempBasalType.Absolute,
|
||||||
|
tbrSettings = DoseSettings(0.05, 30, 12 * 60, 0.0, 25.0),
|
||||||
|
specialBasalDurations = PumpCapability.BasalRate_Duration30minAllowed,
|
||||||
|
baseBasalMinValue = 0.05,
|
||||||
|
baseBasalMaxValue = 25.0,
|
||||||
|
baseBasalStep = 0.05,
|
||||||
|
baseBasalSpecialSteps = null,
|
||||||
|
pumpCapability = PumpCapability.MedtrumCapabilities,
|
||||||
|
isPatchPump = true,
|
||||||
|
maxReservoirReading = 400,
|
||||||
|
source = Source.Medtrum
|
||||||
|
),
|
||||||
|
MEDTRUM_UNTESTED(description = "Medtrum untested", model = "untested", parent = MEDTRUM_NANO);
|
||||||
|
|
||||||
val description: String
|
val description: String
|
||||||
var manufacturer: ManufacturerType? = null
|
var manufacturer: ManufacturerType? = null
|
||||||
|
@ -458,6 +480,7 @@ enum class PumpType {
|
||||||
OmnipodEros,
|
OmnipodEros,
|
||||||
OmnipodDash,
|
OmnipodDash,
|
||||||
EOPatch2,
|
EOPatch2,
|
||||||
|
Medtrum,
|
||||||
MDI,
|
MDI,
|
||||||
VirtualPump,
|
VirtualPump,
|
||||||
Unknown
|
Unknown
|
||||||
|
|
|
@ -23,11 +23,14 @@ abstract class Command(
|
||||||
BASAL_PROFILE,
|
BASAL_PROFILE,
|
||||||
READSTATUS,
|
READSTATUS,
|
||||||
LOAD_HISTORY, // TDDs and so far only Dana specific
|
LOAD_HISTORY, // TDDs and so far only Dana specific
|
||||||
LOAD_EVENTS, // so far only Dana specific
|
LOAD_EVENTS,
|
||||||
LOAD_TDD,
|
LOAD_TDD,
|
||||||
SET_USER_SETTINGS, // so far only Dana specific,
|
SET_USER_SETTINGS, // so far only Dana specific,
|
||||||
START_PUMP,
|
START_PUMP,
|
||||||
STOP_PUMP,
|
STOP_PUMP,
|
||||||
|
CLEAR_ALARMS, // so far only Medtrum specific
|
||||||
|
DEACTIVATE, // so far only Medtrum specific
|
||||||
|
UPDATE_TIME, // so far only Medtrum specific
|
||||||
INSIGHT_SET_TBR_OVER_ALARM, // insight only
|
INSIGHT_SET_TBR_OVER_ALARM, // insight only
|
||||||
CUSTOM_COMMAND
|
CUSTOM_COMMAND
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,9 @@ interface CommandQueue {
|
||||||
fun setUserOptions(callback: Callback?): Boolean
|
fun setUserOptions(callback: Callback?): Boolean
|
||||||
fun loadTDDs(callback: Callback?): Boolean
|
fun loadTDDs(callback: Callback?): Boolean
|
||||||
fun loadEvents(callback: Callback?): Boolean
|
fun loadEvents(callback: Callback?): Boolean
|
||||||
|
fun clearAlarms(callback: Callback?): Boolean
|
||||||
|
fun deactivate(callback: Callback?): Boolean
|
||||||
|
fun updateTime(callback: Callback?): Boolean
|
||||||
fun customCommand(customCommand: CustomCommand, callback: Callback?): Boolean
|
fun customCommand(customCommand: CustomCommand, callback: Callback?): Boolean
|
||||||
fun isCustomCommandRunning(customCommandType: Class<out CustomCommand>): Boolean
|
fun isCustomCommandRunning(customCommandType: Class<out CustomCommand>): Boolean
|
||||||
fun isCustomCommandInQueue(customCommandType: Class<out CustomCommand>): Boolean
|
fun isCustomCommandInQueue(customCommandType: Class<out CustomCommand>): Boolean
|
||||||
|
|
|
@ -59,6 +59,8 @@ fun PumpType.Companion.fromDbPumpType(pt: InterfaceIDs.PumpType): PumpType =
|
||||||
InterfaceIDs.PumpType.USER -> PumpType.USER
|
InterfaceIDs.PumpType.USER -> PumpType.USER
|
||||||
InterfaceIDs.PumpType.DIACONN_G8 -> PumpType.DIACONN_G8
|
InterfaceIDs.PumpType.DIACONN_G8 -> PumpType.DIACONN_G8
|
||||||
InterfaceIDs.PumpType.EOPATCH2 -> PumpType.EOFLOW_EOPATCH2
|
InterfaceIDs.PumpType.EOPATCH2 -> PumpType.EOFLOW_EOPATCH2
|
||||||
|
InterfaceIDs.PumpType.MEDTRUM -> PumpType.MEDTRUM_NANO
|
||||||
|
InterfaceIDs.PumpType.MEDTRUM_UNTESTED -> PumpType.MEDTRUM_UNTESTED
|
||||||
InterfaceIDs.PumpType.CACHE -> PumpType.CACHE
|
InterfaceIDs.PumpType.CACHE -> PumpType.CACHE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,5 +119,7 @@ fun PumpType.toDbPumpType(): InterfaceIDs.PumpType =
|
||||||
PumpType.USER -> InterfaceIDs.PumpType.USER
|
PumpType.USER -> InterfaceIDs.PumpType.USER
|
||||||
PumpType.DIACONN_G8 -> InterfaceIDs.PumpType.DIACONN_G8
|
PumpType.DIACONN_G8 -> InterfaceIDs.PumpType.DIACONN_G8
|
||||||
PumpType.EOFLOW_EOPATCH2 -> InterfaceIDs.PumpType.EOPATCH2
|
PumpType.EOFLOW_EOPATCH2 -> InterfaceIDs.PumpType.EOPATCH2
|
||||||
|
PumpType.MEDTRUM_NANO -> InterfaceIDs.PumpType.MEDTRUM
|
||||||
|
PumpType.MEDTRUM_UNTESTED -> InterfaceIDs.PumpType.MEDTRUM_UNTESTED
|
||||||
PumpType.CACHE -> InterfaceIDs.PumpType.CACHE
|
PumpType.CACHE -> InterfaceIDs.PumpType.CACHE
|
||||||
}
|
}
|
||||||
|
|
71
core/ui/src/main/res/drawable/ic_medtrum_128.xml
Normal file
71
core/ui/src/main/res/drawable/ic_medtrum_128.xml
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="128dp"
|
||||||
|
android:height="128dp"
|
||||||
|
android:viewportWidth="128"
|
||||||
|
android:viewportHeight="128">
|
||||||
|
<path
|
||||||
|
android:pathData="M94.38,15.26H34.37c-16.33,0 -29.56,13.3 -29.56,29.71v38.69c0,16.41 13.24,29.71 29.56,29.71h60.02c16.33,0 29.56,-13.3 29.56,-29.71V44.97C123.95,28.56 110.71,15.26 94.38,15.26zM56.94,106.39c-1.27,0 -2.3,-1.03 -2.3,-2.3c0,-1.27 1.03,-2.3 2.3,-2.3c1.27,0 2.3,1.03 2.3,2.3C59.24,105.36 58.21,106.39 56.94,106.39zM56.94,26.83c-1.27,0 -2.3,-1.03 -2.3,-2.3c0,-1.27 1.03,-2.3 2.3,-2.3c1.27,0 2.3,1.03 2.3,2.3C59.24,25.8 58.21,26.83 56.94,26.83zM112.17,73.32V55.3c2.43,2.24 3.95,5.45 3.95,9.01S114.6,71.08 112.17,73.32z"
|
||||||
|
android:fillColor="#FFFFFF"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M88.27,99.55c5.2,-0.23 9.45,-0.6 11.93,-1.15c8.84,-1.95 12.24,-6.22 12.24,-13.39v-1.26V44.87v-1.26c0,-7.16 -3.4,-11.44 -12.24,-13.39c-2.47,-0.55 -6.73,-0.92 -11.93,-1.15"
|
||||||
|
android:fillColor="#E0E0E0"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M88.53,29.08c-19.86,-0.79 -52.15,0.13 -58.58,1.57c-8.28,1.85 -12.1,5.19 -13.42,12.92c-0.61,3.54 -0.63,12.31 -0.47,20.75c-0.15,8.44 -0.13,17.2 0.47,20.75c1.32,7.72 5.13,11.07 13.42,12.92c6.43,1.43 38.72,2.36 58.58,1.57"
|
||||||
|
android:fillColor="#AAAAAA"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M88.27,98.82c5.01,-0.22 9.12,-0.59 11.5,-1.12c8.53,-1.91 11.81,-6.09 11.81,-13.11v-1.24V45.27V44.03c0,-7.01 -3.28,-11.2 -11.81,-13.11c-2.38,-0.54 -6.49,-0.9 -11.5,-1.12"
|
||||||
|
android:fillColor="#EAEAEA"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M88.53,29.81c-19.57,-0.77 -51.37,0.13 -57.7,1.53c-8.16,1.81 -11.91,5.09 -13.22,12.65c-0.6,3.47 -0.62,12.05 -0.47,20.32c-0.15,8.27 -0.13,16.85 0.47,20.32c1.3,7.56 5.06,10.84 13.22,12.65c6.33,1.4 38.13,2.31 57.7,1.53"
|
||||||
|
android:fillColor="#B2B2B2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M88.27,97.88c4.75,-0.22 8.64,-0.57 10.9,-1.09c8.08,-1.86 11.19,-5.93 11.19,-12.75v-1.2V45.78v-1.2c0,-6.82 -3.11,-10.9 -11.19,-12.75c-2.26,-0.52 -6.15,-0.87 -10.9,-1.09"
|
||||||
|
android:fillColor="#F0F0F0"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M88.53,30.75c-19.15,-0.75 -50.27,0.12 -56.47,1.49c-7.99,1.76 -11.66,4.95 -12.93,12.31c-0.58,3.38 -0.6,11.72 -0.46,19.77c-0.15,8.04 -0.13,16.39 0.46,19.77c1.27,7.36 4.95,10.54 12.93,12.31c6.19,1.37 37.32,2.24 56.47,1.49"
|
||||||
|
android:fillColor="#B9B9B9"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M88.27,96.22c4.24,-0.21 7.72,-0.55 9.73,-1.04c7.22,-1.77 9.99,-5.64 9.99,-12.12v-1.14V46.7v-1.14c0,-6.49 -2.78,-10.36 -9.99,-12.12c-2.02,-0.5 -5.49,-0.83 -9.73,-1.04"
|
||||||
|
android:fillColor="#F4F4F4"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M88.53,32.4c-18.73,-0.71 -49.17,0.12 -55.23,1.42c-7.81,1.67 -11.4,4.7 -12.65,11.7c-0.57,3.21 -0.59,11.15 -0.45,18.79c-0.14,7.65 -0.13,15.58 0.45,18.79c1.25,6.99 4.84,10.02 12.65,11.7c6.06,1.3 36.5,2.13 55.23,1.42"
|
||||||
|
android:fillColor="#BEBEBE"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M59.7,75.53l17.16,0l0,-1.29l10.31,0l0,-19.86l-10.31,0l0,-1.3l-17.16,0"
|
||||||
|
android:strokeWidth="0.4252"
|
||||||
|
android:strokeColor="#9D9DA0">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:startX="59.7"
|
||||||
|
android:startY="64.31"
|
||||||
|
android:endX="87.16"
|
||||||
|
android:endY="64.31"
|
||||||
|
android:type="linear">
|
||||||
|
<item android:offset="0" android:color="#FFBBBDBF"/>
|
||||||
|
<item android:offset="0.12" android:color="#FFB5B7B9"/>
|
||||||
|
<item android:offset="0.51" android:color="#FFB5B7B9"/>
|
||||||
|
<item android:offset="0.58" android:color="#FFB5B7B9"/>
|
||||||
|
<item android:offset="1" android:color="#FF929497"/>
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:pathData="M90.39,68.37c0,1.84 -1.43,3.32 -3.2,3.32H72.75c-1.77,0 -3.2,-1.49 -3.2,-3.32v-8.12c0,-1.84 1.43,-3.32 3.2,-3.32h14.45c1.77,0 3.2,1.49 3.2,3.32V68.37z"
|
||||||
|
android:strokeWidth="0.4252"
|
||||||
|
android:fillColor="#D0D2D3"
|
||||||
|
android:strokeColor="#A3A3A3"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M84.17,74.54c0,4.39 -0.05,4.45 3.63,4.45l0,0c4.39,0 7.95,-3.56 7.95,-7.95v-13.47c0,-4.39 -3.56,-7.95 -7.95,-7.95l0,0c-3.68,0 -3.63,0.1 -3.63,4.49V74.54z"
|
||||||
|
android:strokeWidth="0.2835"
|
||||||
|
android:fillColor="#F4F4F4"
|
||||||
|
android:strokeColor="#C1C1C1"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M94.57,67.68c0,0.83 -0.68,1.51 -1.51,1.51l0,0c-0.83,0 -1.51,-0.68 -1.51,-1.51v-6.74c0,-0.83 0.68,-1.51 1.51,-1.51l0,0c0.83,0 1.51,0.68 1.51,1.51V67.68z"
|
||||||
|
android:strokeWidth="0.2835"
|
||||||
|
android:fillColor="#F1F1F2"
|
||||||
|
android:strokeColor="#B2B2B2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M94.29,61.74c0,0.68 -0.55,1.23 -1.23,1.23l0,0c-0.68,0 -1.23,-0.55 -1.23,-1.23l0,0c0,-0.68 0.55,-1.23 1.23,-1.23l0,0C93.74,60.51 94.29,61.06 94.29,61.74L94.29,61.74z"
|
||||||
|
android:fillColor="#231F20"/>
|
||||||
|
</vector>
|
|
@ -393,6 +393,9 @@
|
||||||
<string name="carbs_g">CARBS %1$d g</string>
|
<string name="carbs_g">CARBS %1$d g</string>
|
||||||
<string name="extended_bolus_u_min">EXTENDED BOLUS %1$.2f U %2$d min</string>
|
<string name="extended_bolus_u_min">EXTENDED BOLUS %1$.2f U %2$d min</string>
|
||||||
<string name="load_events">LOAD EVENTS</string>
|
<string name="load_events">LOAD EVENTS</string>
|
||||||
|
<string name="clear_alarms">CLEAR ALARMS</string>
|
||||||
|
<string name="deactivate">DEACTIVATE</string>
|
||||||
|
<string name="update_time">UPDATE TIME</string>
|
||||||
<string name="load_history">LOAD HISTORY %1$d</string>
|
<string name="load_history">LOAD HISTORY %1$d</string>
|
||||||
<string name="load_tdds">LOAD TDDs</string>
|
<string name="load_tdds">LOAD TDDs</string>
|
||||||
<string name="set_profile">SET PROFILE</string>
|
<string name="set_profile">SET PROFILE</string>
|
||||||
|
|
|
@ -43,6 +43,8 @@ data class InterfaceIDs(
|
||||||
MDI,
|
MDI,
|
||||||
DIACONN_G8,
|
DIACONN_G8,
|
||||||
EOPATCH2,
|
EOPATCH2,
|
||||||
|
MEDTRUM,
|
||||||
|
MEDTRUM_UNTESTED,
|
||||||
USER,
|
USER,
|
||||||
CACHE;
|
CACHE;
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,15 @@ import info.nightscout.implementation.queue.commands.CommandTempBasalPercent
|
||||||
import info.nightscout.implementation.queue.commands.CommandBolus
|
import info.nightscout.implementation.queue.commands.CommandBolus
|
||||||
import info.nightscout.implementation.queue.commands.CommandCancelExtendedBolus
|
import info.nightscout.implementation.queue.commands.CommandCancelExtendedBolus
|
||||||
import info.nightscout.implementation.queue.commands.CommandCancelTempBasal
|
import info.nightscout.implementation.queue.commands.CommandCancelTempBasal
|
||||||
|
import info.nightscout.implementation.queue.commands.CommandClearAlarms
|
||||||
import info.nightscout.implementation.queue.commands.CommandCustomCommand
|
import info.nightscout.implementation.queue.commands.CommandCustomCommand
|
||||||
|
import info.nightscout.implementation.queue.commands.CommandDeactivate
|
||||||
import info.nightscout.implementation.queue.commands.CommandExtendedBolus
|
import info.nightscout.implementation.queue.commands.CommandExtendedBolus
|
||||||
import info.nightscout.implementation.queue.commands.CommandInsightSetTBROverNotification
|
import info.nightscout.implementation.queue.commands.CommandInsightSetTBROverNotification
|
||||||
import info.nightscout.implementation.queue.commands.CommandLoadEvents
|
import info.nightscout.implementation.queue.commands.CommandLoadEvents
|
||||||
import info.nightscout.implementation.queue.commands.CommandLoadHistory
|
import info.nightscout.implementation.queue.commands.CommandLoadHistory
|
||||||
import info.nightscout.implementation.queue.commands.CommandLoadTDDs
|
import info.nightscout.implementation.queue.commands.CommandLoadTDDs
|
||||||
|
import info.nightscout.implementation.queue.commands.CommandUpdateTime
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
|
@ -32,6 +35,9 @@ abstract class CommandQueueModule {
|
||||||
@ContributesAndroidInjector abstract fun commandExtendedBolusInjector(): CommandExtendedBolus
|
@ContributesAndroidInjector abstract fun commandExtendedBolusInjector(): CommandExtendedBolus
|
||||||
@ContributesAndroidInjector abstract fun commandInsightSetTBROverNotificationInjector(): CommandInsightSetTBROverNotification
|
@ContributesAndroidInjector abstract fun commandInsightSetTBROverNotificationInjector(): CommandInsightSetTBROverNotification
|
||||||
@ContributesAndroidInjector abstract fun commandLoadEventsInjector(): CommandLoadEvents
|
@ContributesAndroidInjector abstract fun commandLoadEventsInjector(): CommandLoadEvents
|
||||||
|
@ContributesAndroidInjector abstract fun commandClearAlarmsInjector(): CommandClearAlarms
|
||||||
|
@ContributesAndroidInjector abstract fun commandDeactivateInjector(): CommandDeactivate
|
||||||
|
@ContributesAndroidInjector abstract fun commandUpdateTimeInjector(): CommandUpdateTime
|
||||||
@ContributesAndroidInjector abstract fun commandLoadHistoryInjector(): CommandLoadHistory
|
@ContributesAndroidInjector abstract fun commandLoadHistoryInjector(): CommandLoadHistory
|
||||||
@ContributesAndroidInjector abstract fun commandLoadTDDsInjector(): CommandLoadTDDs
|
@ContributesAndroidInjector abstract fun commandLoadTDDsInjector(): CommandLoadTDDs
|
||||||
@ContributesAndroidInjector abstract fun commandReadStatusInjector(): CommandReadStatus
|
@ContributesAndroidInjector abstract fun commandReadStatusInjector(): CommandReadStatus
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
package info.nightscout.implementation.pump
|
package info.nightscout.implementation.pump
|
||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
import info.nightscout.androidaps.annotations.OpenForTesting
|
import info.nightscout.androidaps.annotations.OpenForTesting
|
||||||
|
import info.nightscout.implementation.R
|
||||||
import info.nightscout.interfaces.pump.DetailedBolusInfo
|
import info.nightscout.interfaces.pump.DetailedBolusInfo
|
||||||
import info.nightscout.interfaces.pump.DetailedBolusInfoStorage
|
import info.nightscout.interfaces.pump.DetailedBolusInfoStorage
|
||||||
import info.nightscout.rx.logging.AAPSLogger
|
import info.nightscout.rx.logging.AAPSLogger
|
||||||
|
import info.nightscout.shared.sharedPreferences.SP
|
||||||
import info.nightscout.rx.logging.LTag
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import info.nightscout.shared.interfaces.ResourceHelper
|
||||||
import info.nightscout.shared.utils.T
|
import info.nightscout.shared.utils.T
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -14,10 +18,12 @@ import kotlin.math.abs
|
||||||
@OpenForTesting
|
@OpenForTesting
|
||||||
@Singleton
|
@Singleton
|
||||||
class DetailedBolusInfoStorageImpl @Inject constructor(
|
class DetailedBolusInfoStorageImpl @Inject constructor(
|
||||||
val aapsLogger: AAPSLogger
|
val aapsLogger: AAPSLogger,
|
||||||
|
val sp: SP,
|
||||||
|
val rh: ResourceHelper
|
||||||
) : DetailedBolusInfoStorage {
|
) : DetailedBolusInfoStorage {
|
||||||
|
|
||||||
val store = ArrayList<DetailedBolusInfo>()
|
val store = loadStore()
|
||||||
|
|
||||||
fun DetailedBolusInfo.toJsonString(): String = Gson().toJson(this)
|
fun DetailedBolusInfo.toJsonString(): String = Gson().toJson(this)
|
||||||
|
|
||||||
|
@ -25,6 +31,7 @@ class DetailedBolusInfoStorageImpl @Inject constructor(
|
||||||
override fun add(detailedBolusInfo: DetailedBolusInfo) {
|
override fun add(detailedBolusInfo: DetailedBolusInfo) {
|
||||||
aapsLogger.debug("Stored bolus info: ${detailedBolusInfo.toJsonString()}")
|
aapsLogger.debug("Stored bolus info: ${detailedBolusInfo.toJsonString()}")
|
||||||
store.add(detailedBolusInfo)
|
store.add(detailedBolusInfo)
|
||||||
|
saveStore()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
|
@ -36,6 +43,7 @@ class DetailedBolusInfoStorageImpl @Inject constructor(
|
||||||
if (bolusTime > d.timestamp - T.mins(1).msecs() && bolusTime < d.timestamp + T.mins(1).msecs() && abs(store[i].insulin - bolus) < 0.01) {
|
if (bolusTime > d.timestamp - T.mins(1).msecs() && bolusTime < d.timestamp + T.mins(1).msecs() && abs(store[i].insulin - bolus) < 0.01) {
|
||||||
aapsLogger.debug(LTag.PUMP, "Using & removing bolus info for time $bolusTime: ${store[i]}")
|
aapsLogger.debug(LTag.PUMP, "Using & removing bolus info for time $bolusTime: ${store[i]}")
|
||||||
store.removeAt(i)
|
store.removeAt(i)
|
||||||
|
saveStore()
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +53,7 @@ class DetailedBolusInfoStorageImpl @Inject constructor(
|
||||||
if (bolusTime > d.timestamp - T.mins(1).msecs() && bolusTime < d.timestamp + T.mins(1).msecs() && bolus <= store[i].insulin + 0.01) {
|
if (bolusTime > d.timestamp - T.mins(1).msecs() && bolusTime < d.timestamp + T.mins(1).msecs() && bolus <= store[i].insulin + 0.01) {
|
||||||
aapsLogger.debug(LTag.PUMP, "Using TIME-ONLY & removing bolus info for time $bolusTime: ${store[i]}")
|
aapsLogger.debug(LTag.PUMP, "Using TIME-ONLY & removing bolus info for time $bolusTime: ${store[i]}")
|
||||||
store.removeAt(i)
|
store.removeAt(i)
|
||||||
|
saveStore()
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,4 +70,24 @@ class DetailedBolusInfoStorageImpl @Inject constructor(
|
||||||
aapsLogger.debug(LTag.PUMP, "Bolus info not found for time $bolusTime")
|
aapsLogger.debug(LTag.PUMP, "Bolus info not found for time $bolusTime")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun saveStore() {
|
||||||
|
var lastTwoEntries = store
|
||||||
|
// Only save last two entries, to avoid too much data in preferences
|
||||||
|
if (store.size > 2) {
|
||||||
|
lastTwoEntries = ArrayList(store.subList(store.size - 2, store.size))
|
||||||
|
}
|
||||||
|
val jsonString = Gson().toJson(lastTwoEntries)
|
||||||
|
sp.putString(rh.gs(R.string.key_bolus_storage), jsonString)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadStore(): ArrayList<DetailedBolusInfo> {
|
||||||
|
val jsonString = sp.getString(rh.gs(R.string.key_bolus_storage), "")
|
||||||
|
return if (jsonString != null && jsonString.isNotEmpty()) {
|
||||||
|
val type = object : TypeToken<List<DetailedBolusInfo>>() {}.type
|
||||||
|
Gson().fromJson(jsonString, type)
|
||||||
|
} else {
|
||||||
|
ArrayList()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -22,7 +22,9 @@ import info.nightscout.implementation.R
|
||||||
import info.nightscout.implementation.queue.commands.CommandBolus
|
import info.nightscout.implementation.queue.commands.CommandBolus
|
||||||
import info.nightscout.implementation.queue.commands.CommandCancelExtendedBolus
|
import info.nightscout.implementation.queue.commands.CommandCancelExtendedBolus
|
||||||
import info.nightscout.implementation.queue.commands.CommandCancelTempBasal
|
import info.nightscout.implementation.queue.commands.CommandCancelTempBasal
|
||||||
|
import info.nightscout.implementation.queue.commands.CommandClearAlarms
|
||||||
import info.nightscout.implementation.queue.commands.CommandCustomCommand
|
import info.nightscout.implementation.queue.commands.CommandCustomCommand
|
||||||
|
import info.nightscout.implementation.queue.commands.CommandDeactivate
|
||||||
import info.nightscout.implementation.queue.commands.CommandExtendedBolus
|
import info.nightscout.implementation.queue.commands.CommandExtendedBolus
|
||||||
import info.nightscout.implementation.queue.commands.CommandInsightSetTBROverNotification
|
import info.nightscout.implementation.queue.commands.CommandInsightSetTBROverNotification
|
||||||
import info.nightscout.implementation.queue.commands.CommandLoadEvents
|
import info.nightscout.implementation.queue.commands.CommandLoadEvents
|
||||||
|
@ -36,6 +38,7 @@ import info.nightscout.implementation.queue.commands.CommandStartPump
|
||||||
import info.nightscout.implementation.queue.commands.CommandStopPump
|
import info.nightscout.implementation.queue.commands.CommandStopPump
|
||||||
import info.nightscout.implementation.queue.commands.CommandTempBasalAbsolute
|
import info.nightscout.implementation.queue.commands.CommandTempBasalAbsolute
|
||||||
import info.nightscout.implementation.queue.commands.CommandTempBasalPercent
|
import info.nightscout.implementation.queue.commands.CommandTempBasalPercent
|
||||||
|
import info.nightscout.implementation.queue.commands.CommandUpdateTime
|
||||||
import info.nightscout.interfaces.AndroidPermission
|
import info.nightscout.interfaces.AndroidPermission
|
||||||
import info.nightscout.interfaces.Config
|
import info.nightscout.interfaces.Config
|
||||||
import info.nightscout.interfaces.constraints.Constraint
|
import info.nightscout.interfaces.constraints.Constraint
|
||||||
|
@ -535,6 +538,46 @@ class CommandQueueImplementation @Inject constructor(
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns true if command is queued
|
||||||
|
override fun clearAlarms(callback: Callback?): Boolean {
|
||||||
|
if (isRunning(CommandType.CLEAR_ALARMS)) {
|
||||||
|
callback?.result(executingNowError())?.run()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// remove all unfinished
|
||||||
|
removeAll(CommandType.CLEAR_ALARMS)
|
||||||
|
// add new command to queue
|
||||||
|
add(CommandClearAlarms(injector, callback))
|
||||||
|
notifyAboutNewCommand()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deactivate(callback: Callback?): Boolean {
|
||||||
|
if (isRunning(CommandType.DEACTIVATE)) {
|
||||||
|
callback?.result(executingNowError())?.run()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// remove all unfinished
|
||||||
|
removeAll(CommandType.DEACTIVATE)
|
||||||
|
// add new command to queue
|
||||||
|
add(CommandDeactivate(injector, callback))
|
||||||
|
notifyAboutNewCommand()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateTime(callback: Callback?): Boolean {
|
||||||
|
if (isRunning(CommandType.UPDATE_TIME)) {
|
||||||
|
callback?.result(executingNowError())?.run()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// remove all unfinished
|
||||||
|
removeAll(CommandType.UPDATE_TIME)
|
||||||
|
// add new command to queue
|
||||||
|
add(CommandUpdateTime(injector, callback))
|
||||||
|
notifyAboutNewCommand()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
override fun customCommand(customCommand: CustomCommand, callback: Callback?): Boolean {
|
override fun customCommand(customCommand: CustomCommand, callback: Callback?): Boolean {
|
||||||
if (isCustomCommandInQueue(customCommand.javaClass)) {
|
if (isCustomCommandInQueue(customCommand.javaClass)) {
|
||||||
callback?.result(executingNowError())?.run()
|
callback?.result(executingNowError())?.run()
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package info.nightscout.implementation.queue.commands
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.interfaces.plugin.ActivePlugin
|
||||||
|
import info.nightscout.interfaces.pump.Dana
|
||||||
|
import info.nightscout.interfaces.pump.Diaconn
|
||||||
|
import info.nightscout.interfaces.pump.Medtrum
|
||||||
|
import info.nightscout.interfaces.pump.PumpEnactResult
|
||||||
|
import info.nightscout.interfaces.queue.Callback
|
||||||
|
import info.nightscout.interfaces.queue.Command
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CommandClearAlarms(
|
||||||
|
injector: HasAndroidInjector,
|
||||||
|
callback: Callback?
|
||||||
|
) : Command(injector, CommandType.CLEAR_ALARMS, callback) {
|
||||||
|
|
||||||
|
@Inject lateinit var activePlugin: ActivePlugin
|
||||||
|
|
||||||
|
override fun execute() {
|
||||||
|
val pump = activePlugin.activePump
|
||||||
|
|
||||||
|
if (pump is Medtrum) {
|
||||||
|
val medtrumPump = pump as Medtrum
|
||||||
|
val r = medtrumPump.clearAlarms()
|
||||||
|
aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
|
||||||
|
callback?.result(r)?.run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun status(): String = rh.gs(info.nightscout.core.ui.R.string.clear_alarms)
|
||||||
|
|
||||||
|
override fun log(): String = "CLEAR ALARMS"
|
||||||
|
override fun cancel() {
|
||||||
|
aapsLogger.debug(LTag.PUMPQUEUE, "Result cancel")
|
||||||
|
callback?.result(PumpEnactResult(injector).success(false).comment(info.nightscout.core.ui.R.string.connectiontimedout))?.run()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package info.nightscout.implementation.queue.commands
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.interfaces.plugin.ActivePlugin
|
||||||
|
import info.nightscout.interfaces.pump.Dana
|
||||||
|
import info.nightscout.interfaces.pump.Diaconn
|
||||||
|
import info.nightscout.interfaces.pump.Medtrum
|
||||||
|
import info.nightscout.interfaces.pump.PumpEnactResult
|
||||||
|
import info.nightscout.interfaces.queue.Callback
|
||||||
|
import info.nightscout.interfaces.queue.Command
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CommandDeactivate(
|
||||||
|
injector: HasAndroidInjector,
|
||||||
|
callback: Callback?
|
||||||
|
) : Command(injector, CommandType.DEACTIVATE, callback) {
|
||||||
|
|
||||||
|
@Inject lateinit var activePlugin: ActivePlugin
|
||||||
|
|
||||||
|
override fun execute() {
|
||||||
|
val pump = activePlugin.activePump
|
||||||
|
|
||||||
|
if (pump is Medtrum) {
|
||||||
|
val medtrumPump = pump as Medtrum
|
||||||
|
val r = medtrumPump.deactivate()
|
||||||
|
aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
|
||||||
|
callback?.result(r)?.run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun status(): String = rh.gs(info.nightscout.core.ui.R.string.deactivate)
|
||||||
|
|
||||||
|
override fun log(): String = "DEACTIVATE"
|
||||||
|
override fun cancel() {
|
||||||
|
aapsLogger.debug(LTag.PUMPQUEUE, "Result cancel")
|
||||||
|
callback?.result(PumpEnactResult(injector).success(false).comment(info.nightscout.core.ui.R.string.connectiontimedout))?.run()
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import dagger.android.HasAndroidInjector
|
||||||
import info.nightscout.interfaces.plugin.ActivePlugin
|
import info.nightscout.interfaces.plugin.ActivePlugin
|
||||||
import info.nightscout.interfaces.pump.Dana
|
import info.nightscout.interfaces.pump.Dana
|
||||||
import info.nightscout.interfaces.pump.Diaconn
|
import info.nightscout.interfaces.pump.Diaconn
|
||||||
|
import info.nightscout.interfaces.pump.Medtrum
|
||||||
import info.nightscout.interfaces.pump.PumpEnactResult
|
import info.nightscout.interfaces.pump.PumpEnactResult
|
||||||
import info.nightscout.interfaces.queue.Callback
|
import info.nightscout.interfaces.queue.Callback
|
||||||
import info.nightscout.interfaces.queue.Command
|
import info.nightscout.interfaces.queue.Command
|
||||||
|
@ -32,6 +33,13 @@ class CommandLoadEvents(
|
||||||
aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
|
aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
|
||||||
callback?.result(r)?.run()
|
callback?.result(r)?.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pump is Medtrum) {
|
||||||
|
val medtrumPump = pump as Medtrum
|
||||||
|
val r = medtrumPump.loadEvents()
|
||||||
|
aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
|
||||||
|
callback?.result(r)?.run()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun status(): String = rh.gs(info.nightscout.core.ui.R.string.load_events)
|
override fun status(): String = rh.gs(info.nightscout.core.ui.R.string.load_events)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import dagger.android.HasAndroidInjector
|
||||||
import info.nightscout.interfaces.plugin.ActivePlugin
|
import info.nightscout.interfaces.plugin.ActivePlugin
|
||||||
import info.nightscout.interfaces.pump.Dana
|
import info.nightscout.interfaces.pump.Dana
|
||||||
import info.nightscout.interfaces.pump.Diaconn
|
import info.nightscout.interfaces.pump.Diaconn
|
||||||
|
import info.nightscout.interfaces.pump.Medtrum
|
||||||
import info.nightscout.interfaces.pump.PumpEnactResult
|
import info.nightscout.interfaces.pump.PumpEnactResult
|
||||||
import info.nightscout.interfaces.queue.Callback
|
import info.nightscout.interfaces.queue.Callback
|
||||||
import info.nightscout.interfaces.queue.Command
|
import info.nightscout.interfaces.queue.Command
|
||||||
|
@ -30,6 +31,12 @@ class CommandSetUserSettings(
|
||||||
aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
|
aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
|
||||||
callback?.result(r)?.run()
|
callback?.result(r)?.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pump is Medtrum) {
|
||||||
|
val r = pump.setUserOptions()
|
||||||
|
aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
|
||||||
|
callback?.result(r)?.run()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun status(): String = rh.gs(info.nightscout.core.ui.R.string.set_user_settings)
|
override fun status(): String = rh.gs(info.nightscout.core.ui.R.string.set_user_settings)
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package info.nightscout.implementation.queue.commands
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.interfaces.plugin.ActivePlugin
|
||||||
|
import info.nightscout.interfaces.pump.Dana
|
||||||
|
import info.nightscout.interfaces.pump.Diaconn
|
||||||
|
import info.nightscout.interfaces.pump.Medtrum
|
||||||
|
import info.nightscout.interfaces.pump.PumpEnactResult
|
||||||
|
import info.nightscout.interfaces.queue.Callback
|
||||||
|
import info.nightscout.interfaces.queue.Command
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CommandUpdateTime(
|
||||||
|
injector: HasAndroidInjector,
|
||||||
|
callback: Callback?
|
||||||
|
) : Command(injector, CommandType.UPDATE_TIME, callback) {
|
||||||
|
|
||||||
|
@Inject lateinit var activePlugin: ActivePlugin
|
||||||
|
|
||||||
|
override fun execute() {
|
||||||
|
val pump = activePlugin.activePump
|
||||||
|
|
||||||
|
if (pump is Medtrum) {
|
||||||
|
val medtrumPump = pump as Medtrum
|
||||||
|
val r = medtrumPump.updateTime()
|
||||||
|
aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
|
||||||
|
callback?.result(r)?.run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun status(): String = rh.gs(info.nightscout.core.ui.R.string.update_time)
|
||||||
|
|
||||||
|
override fun log(): String = "UPDATE TIME"
|
||||||
|
override fun cancel() {
|
||||||
|
aapsLogger.debug(LTag.PUMPQUEUE, "Result cancel")
|
||||||
|
callback?.result(PumpEnactResult(injector).success(false).comment(info.nightscout.core.ui.R.string.connectiontimedout))?.run()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<string name="key_bolus_storage" translatable="false">key_bolus_storage</string>
|
||||||
<string name="bg_label">BG</string>
|
<string name="bg_label">BG</string>
|
||||||
|
|
||||||
<string name="executing_right_now">Command is executed right now</string>
|
<string name="executing_right_now">Command is executed right now</string>
|
||||||
|
|
|
@ -2,13 +2,19 @@ package info.nightscout.implementation.pump
|
||||||
|
|
||||||
import info.nightscout.androidaps.TestBase
|
import info.nightscout.androidaps.TestBase
|
||||||
import info.nightscout.interfaces.pump.DetailedBolusInfo
|
import info.nightscout.interfaces.pump.DetailedBolusInfo
|
||||||
|
import info.nightscout.shared.interfaces.ResourceHelper
|
||||||
|
import info.nightscout.shared.sharedPreferences.SP
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.Mock
|
||||||
|
|
||||||
class DetailedBolusInfoStorageTest : TestBase() {
|
class DetailedBolusInfoStorageTest : TestBase() {
|
||||||
|
|
||||||
|
@Mock lateinit var sp: SP
|
||||||
|
@Mock lateinit var rh: ResourceHelper
|
||||||
|
|
||||||
private val info1 = DetailedBolusInfo()
|
private val info1 = DetailedBolusInfo()
|
||||||
private val info2 = DetailedBolusInfo()
|
private val info2 = DetailedBolusInfo()
|
||||||
private val info3 = DetailedBolusInfo()
|
private val info3 = DetailedBolusInfo()
|
||||||
|
@ -26,7 +32,7 @@ class DetailedBolusInfoStorageTest : TestBase() {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun prepare() {
|
fun prepare() {
|
||||||
detailedBolusInfoStorage = DetailedBolusInfoStorageImpl(aapsLogger)
|
detailedBolusInfoStorage = DetailedBolusInfoStorageImpl(aapsLogger, sp, rh)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setUp() {
|
private fun setUp() {
|
||||||
|
|
|
@ -238,6 +238,19 @@ class CommandQueueImplementationTest : TestBaseWithProfile() {
|
||||||
// add loadEvents
|
// add loadEvents
|
||||||
commandQueue.loadEvents(null)
|
commandQueue.loadEvents(null)
|
||||||
Assertions.assertEquals(4, commandQueue.size())
|
Assertions.assertEquals(4, commandQueue.size())
|
||||||
|
|
||||||
|
// add clearAlarms
|
||||||
|
commandQueue.clearAlarms(null)
|
||||||
|
Assertions.assertEquals(5, commandQueue.size())
|
||||||
|
|
||||||
|
// add deactivate
|
||||||
|
commandQueue.deactivate(null)
|
||||||
|
Assertions.assertEquals(6, commandQueue.size())
|
||||||
|
|
||||||
|
// add updateTime
|
||||||
|
commandQueue.updateTime(null)
|
||||||
|
Assertions.assertEquals(7, commandQueue.size())
|
||||||
|
|
||||||
commandQueue.clear()
|
commandQueue.clear()
|
||||||
commandQueue.tempBasalAbsolute(0.0, 30, true, validProfile, PumpSync.TemporaryBasalType.NORMAL, null)
|
commandQueue.tempBasalAbsolute(0.0, 30, true, validProfile, PumpSync.TemporaryBasalType.NORMAL, null)
|
||||||
commandQueue.pickup()
|
commandQueue.pickup()
|
||||||
|
@ -354,6 +367,54 @@ class CommandQueueImplementationTest : TestBaseWithProfile() {
|
||||||
Assertions.assertEquals(1, commandQueue.size())
|
Assertions.assertEquals(1, commandQueue.size())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isClearAlarmsCommandInQueue() {
|
||||||
|
// given
|
||||||
|
Assertions.assertEquals(0, commandQueue.size())
|
||||||
|
|
||||||
|
// when
|
||||||
|
commandQueue.clearAlarms(null)
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assertions.assertTrue(commandQueue.isLastScheduled(Command.CommandType.CLEAR_ALARMS))
|
||||||
|
Assertions.assertEquals(1, commandQueue.size())
|
||||||
|
// next should be ignored
|
||||||
|
commandQueue.clearAlarms(null)
|
||||||
|
Assertions.assertEquals(1, commandQueue.size())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isDeactivateCommandInQueue() {
|
||||||
|
// given
|
||||||
|
Assertions.assertEquals(0, commandQueue.size())
|
||||||
|
|
||||||
|
// when
|
||||||
|
commandQueue.deactivate(null)
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assertions.assertTrue(commandQueue.isLastScheduled(Command.CommandType.DEACTIVATE))
|
||||||
|
Assertions.assertEquals(1, commandQueue.size())
|
||||||
|
// next should be ignored
|
||||||
|
commandQueue.deactivate(null)
|
||||||
|
Assertions.assertEquals(1, commandQueue.size())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isUpdateTimeCommandInQueue() {
|
||||||
|
// given
|
||||||
|
Assertions.assertEquals(0, commandQueue.size())
|
||||||
|
|
||||||
|
// when
|
||||||
|
commandQueue.updateTime(null)
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assertions.assertTrue(commandQueue.isLastScheduled(Command.CommandType.UPDATE_TIME))
|
||||||
|
Assertions.assertEquals(1, commandQueue.size())
|
||||||
|
// next should be ignored
|
||||||
|
commandQueue.updateTime(null)
|
||||||
|
Assertions.assertEquals(1, commandQueue.size())
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun isLoadTDDsCommandInQueue() {
|
fun isLoadTDDsCommandInQueue() {
|
||||||
// given
|
// given
|
||||||
|
|
|
@ -35,6 +35,7 @@ import info.nightscout.interfaces.plugin.ActivePlugin
|
||||||
import info.nightscout.interfaces.plugin.PluginBase
|
import info.nightscout.interfaces.plugin.PluginBase
|
||||||
import info.nightscout.interfaces.plugin.PluginType
|
import info.nightscout.interfaces.plugin.PluginType
|
||||||
import info.nightscout.interfaces.profile.ProfileFunction
|
import info.nightscout.interfaces.profile.ProfileFunction
|
||||||
|
import info.nightscout.interfaces.pump.Medtrum
|
||||||
import info.nightscout.interfaces.pump.OmnipodDash
|
import info.nightscout.interfaces.pump.OmnipodDash
|
||||||
import info.nightscout.interfaces.pump.OmnipodEros
|
import info.nightscout.interfaces.pump.OmnipodEros
|
||||||
import info.nightscout.interfaces.queue.CommandQueue
|
import info.nightscout.interfaces.queue.CommandQueue
|
||||||
|
@ -319,23 +320,24 @@ class SWDefinition @Inject constructor(
|
||||||
.text(R.string.readstatus)
|
.text(R.string.readstatus)
|
||||||
.action { commandQueue.readStatus(rh.gs(info.nightscout.core.ui.R.string.clicked_connect_to_pump), null) }
|
.action { commandQueue.readStatus(rh.gs(info.nightscout.core.ui.R.string.clicked_connect_to_pump), null) }
|
||||||
.visibility {
|
.visibility {
|
||||||
// Hide for Omnipod, because as we don't require a Pod to be paired in the setup wizard,
|
// Hide for Omnipod and Medtrum, because as we don't require a Pod/Patch to be paired in the setup wizard,
|
||||||
// Getting the status might not be possible
|
// Getting the status might not be possible
|
||||||
activePlugin.activePump !is OmnipodEros && activePlugin.activePump !is OmnipodDash
|
activePlugin.activePump !is OmnipodEros && activePlugin.activePump !is OmnipodDash && activePlugin.activePump !is Medtrum
|
||||||
})
|
})
|
||||||
.add(SWEventListener(injector, EventPumpStatusChanged::class.java)
|
.add(SWEventListener(injector, EventPumpStatusChanged::class.java)
|
||||||
.visibility { activePlugin.activePump !is OmnipodEros && activePlugin.activePump !is OmnipodDash })
|
.visibility { activePlugin.activePump !is OmnipodEros && activePlugin.activePump !is OmnipodDash && activePlugin.activePump !is Medtrum })
|
||||||
.validator { isPumpInitialized() }
|
.validator { isPumpInitialized() }
|
||||||
|
|
||||||
private fun isPumpInitialized(): Boolean {
|
private fun isPumpInitialized(): Boolean {
|
||||||
val activePump = activePlugin.activePump
|
val activePump = activePlugin.activePump
|
||||||
|
|
||||||
// For Omnipod, activating a Pod can be done after setup through the Omnipod fragment
|
// For Omnipod and Medtrum, activating a Pod/Patch can be done after setup through the pump fragment
|
||||||
// For the Eros model, consider the pump initialized when a RL has been configured successfully
|
// For the Eros, consider the pump initialized when a RL has been configured successfully
|
||||||
// For Dash model, consider the pump setup without any extra conditions
|
// For all others, consider the pump setup without any extra conditions
|
||||||
return activePump.isInitialized()
|
return activePump.isInitialized()
|
||||||
|| (activePump is OmnipodEros && activePump.isRileyLinkReady())
|
|| (activePump is OmnipodEros && activePump.isRileyLinkReady())
|
||||||
|| activePump is OmnipodDash
|
|| activePump is OmnipodDash
|
||||||
|
|| activePump is Medtrum
|
||||||
}
|
}
|
||||||
|
|
||||||
private val screenAps
|
private val screenAps
|
||||||
|
|
|
@ -2226,7 +2226,7 @@ class ComboV2Plugin @Inject constructor (
|
||||||
// only shows up in the Combo fragment.
|
// only shows up in the Combo fragment.
|
||||||
if (newState == DriverState.Suspended) {
|
if (newState == DriverState.Suspended) {
|
||||||
uiInteraction.addNotification(
|
uiInteraction.addNotification(
|
||||||
Notification.COMBO_PUMP_SUSPENDED,
|
Notification.PUMP_SUSPENDED,
|
||||||
text = rh.gs(R.string.combov2_pump_is_suspended),
|
text = rh.gs(R.string.combov2_pump_is_suspended),
|
||||||
level = Notification.NORMAL
|
level = Notification.NORMAL
|
||||||
)
|
)
|
||||||
|
|
1
pump/medtrum/.gitignore
vendored
Normal file
1
pump/medtrum/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build
|
33
pump/medtrum/build.gradle
Normal file
33
pump/medtrum/build.gradle
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
plugins {
|
||||||
|
id 'com.android.library'
|
||||||
|
id 'kotlin-android'
|
||||||
|
id 'kotlin-kapt'
|
||||||
|
id 'kotlin-allopen'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "${project.rootDir}/core/main/android_dependencies.gradle"
|
||||||
|
apply from: "${project.rootDir}/core/main/android_module_dependencies.gradle"
|
||||||
|
apply from: "${project.rootDir}/core/main/allopen_dependencies.gradle"
|
||||||
|
apply from: "${project.rootDir}/core/main/test_dependencies.gradle"
|
||||||
|
apply from: "${project.rootDir}/core/main/jacoco_global.gradle"
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace 'info.nightscout.pump.medtrum'
|
||||||
|
dataBinding {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(':app-wear-shared:shared')
|
||||||
|
implementation project(':database:entities')
|
||||||
|
implementation project(':core:libraries')
|
||||||
|
implementation project(':core:interfaces')
|
||||||
|
implementation project(':core:main')
|
||||||
|
implementation project(':core:ui')
|
||||||
|
implementation project(':core:validators')
|
||||||
|
implementation project(':pump:pump-common')
|
||||||
|
implementation project(':core:utils')
|
||||||
|
|
||||||
|
testImplementation project(':core:main')
|
||||||
|
}
|
0
pump/medtrum/consumer-rules.pro
Normal file
0
pump/medtrum/consumer-rules.pro
Normal file
21
pump/medtrum/proguard-rules.pro
vendored
Normal file
21
pump/medtrum/proguard-rules.pro
vendored
Normal 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
|
17
pump/medtrum/src/main/AndroidManifest.xml
Normal file
17
pump/medtrum/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<activity android:name=".ui.MedtrumActivity" />
|
||||||
|
<service
|
||||||
|
android:name=".services.MedtrumService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
|
@ -0,0 +1,472 @@
|
||||||
|
package info.nightscout.pump.medtrum
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.ServiceConnection
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.text.format.DateFormat
|
||||||
|
import androidx.preference.EditTextPreference
|
||||||
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.core.ui.toast.ToastUtils
|
||||||
|
import info.nightscout.core.utils.fabric.FabricPrivacy
|
||||||
|
import info.nightscout.interfaces.constraints.Constraint
|
||||||
|
import info.nightscout.interfaces.constraints.Constraints
|
||||||
|
import info.nightscout.interfaces.notifications.Notification
|
||||||
|
import info.nightscout.interfaces.plugin.PluginDescription
|
||||||
|
import info.nightscout.interfaces.plugin.PluginType
|
||||||
|
import info.nightscout.interfaces.profile.Profile
|
||||||
|
import info.nightscout.interfaces.pump.DetailedBolusInfo
|
||||||
|
import info.nightscout.interfaces.pump.Medtrum
|
||||||
|
import info.nightscout.interfaces.pump.Pump
|
||||||
|
import info.nightscout.interfaces.pump.PumpEnactResult
|
||||||
|
import info.nightscout.interfaces.pump.PumpPluginBase
|
||||||
|
import info.nightscout.interfaces.pump.PumpSync
|
||||||
|
import info.nightscout.interfaces.pump.TemporaryBasalStorage
|
||||||
|
import info.nightscout.interfaces.pump.actions.CustomAction
|
||||||
|
import info.nightscout.interfaces.pump.actions.CustomActionType
|
||||||
|
import info.nightscout.interfaces.pump.defs.ManufacturerType
|
||||||
|
import info.nightscout.interfaces.pump.defs.PumpDescription
|
||||||
|
import info.nightscout.interfaces.pump.defs.PumpType
|
||||||
|
import info.nightscout.interfaces.queue.Callback
|
||||||
|
import info.nightscout.interfaces.queue.CommandQueue
|
||||||
|
import info.nightscout.interfaces.queue.CustomCommand
|
||||||
|
import info.nightscout.interfaces.ui.UiInteraction
|
||||||
|
import info.nightscout.interfaces.utils.DecimalFormatter
|
||||||
|
import info.nightscout.interfaces.utils.TimeChangeType
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumOverviewFragment
|
||||||
|
import info.nightscout.pump.medtrum.services.MedtrumService
|
||||||
|
import info.nightscout.rx.AapsSchedulers
|
||||||
|
import info.nightscout.rx.bus.RxBus
|
||||||
|
import info.nightscout.rx.events.EventAppExit
|
||||||
|
import info.nightscout.rx.events.EventDismissNotification
|
||||||
|
import info.nightscout.rx.events.EventOverviewBolusProgress
|
||||||
|
import info.nightscout.rx.logging.AAPSLogger
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import info.nightscout.shared.interfaces.ResourceHelper
|
||||||
|
import info.nightscout.shared.utils.DateUtil
|
||||||
|
import info.nightscout.shared.utils.T
|
||||||
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
|
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||||
|
import org.json.JSONException
|
||||||
|
import org.json.JSONObject
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
@Singleton class MedtrumPlugin @Inject constructor(
|
||||||
|
injector: HasAndroidInjector,
|
||||||
|
aapsLogger: AAPSLogger,
|
||||||
|
rh: ResourceHelper,
|
||||||
|
commandQueue: CommandQueue,
|
||||||
|
private val constraintChecker: Constraints,
|
||||||
|
private val aapsSchedulers: AapsSchedulers,
|
||||||
|
private val rxBus: RxBus,
|
||||||
|
private val context: Context,
|
||||||
|
private val fabricPrivacy: FabricPrivacy,
|
||||||
|
private val dateUtil: DateUtil,
|
||||||
|
private val medtrumPump: MedtrumPump,
|
||||||
|
private val uiInteraction: UiInteraction,
|
||||||
|
private val pumpSync: PumpSync,
|
||||||
|
private val temporaryBasalStorage: TemporaryBasalStorage
|
||||||
|
) : PumpPluginBase(
|
||||||
|
PluginDescription()
|
||||||
|
.mainType(PluginType.PUMP)
|
||||||
|
.fragmentClass(MedtrumOverviewFragment::class.java.name)
|
||||||
|
.pluginIcon(info.nightscout.core.ui.R.drawable.ic_medtrum_128)
|
||||||
|
.pluginName(R.string.medtrum)
|
||||||
|
.shortName(R.string.medtrum_pump_shortname)
|
||||||
|
.preferencesId(R.xml.pref_medtrum_pump)
|
||||||
|
.description(R.string.medtrum_pump_description), injector, aapsLogger, rh, commandQueue
|
||||||
|
), Pump, Medtrum {
|
||||||
|
|
||||||
|
private val disposable = CompositeDisposable()
|
||||||
|
private var medtrumService: MedtrumService? = null
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
aapsLogger.debug(LTag.PUMP, "MedtrumPlugin onStart()")
|
||||||
|
val intent = Intent(context, MedtrumService::class.java)
|
||||||
|
context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
|
||||||
|
disposable += rxBus
|
||||||
|
.toObservable(EventAppExit::class.java)
|
||||||
|
.observeOn(aapsSchedulers.io)
|
||||||
|
.subscribe({ context.unbindService(mConnection) }, fabricPrivacy::logException)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "MedtrumPlugin onStop()")
|
||||||
|
context.unbindService(mConnection)
|
||||||
|
disposable.clear()
|
||||||
|
super.onStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val mConnection: ServiceConnection = object : ServiceConnection {
|
||||||
|
override fun onServiceDisconnected(name: ComponentName) {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "Service is disconnected")
|
||||||
|
medtrumService = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "Service is connected")
|
||||||
|
val mLocalBinder = service as MedtrumService.LocalBinder
|
||||||
|
medtrumService = mLocalBinder.serviceInstance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getService(): MedtrumService? {
|
||||||
|
return medtrumService
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun preprocessPreferences(preferenceFragment: PreferenceFragmentCompat) {
|
||||||
|
super.preprocessPreferences(preferenceFragment)
|
||||||
|
val serialSetting = preferenceFragment.findPreference(rh.gs(R.string.key_sn_input)) as EditTextPreference?
|
||||||
|
serialSetting?.isEnabled = !isInitialized()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isInitialized(): Boolean {
|
||||||
|
return medtrumPump.pumpState > MedtrumPumpState.EJECTED && medtrumPump.pumpState < MedtrumPumpState.STOPPED
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isSuspended(): Boolean {
|
||||||
|
return medtrumPump.pumpState < MedtrumPumpState.ACTIVE || medtrumPump.pumpState > MedtrumPumpState.ACTIVE_ALT
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isBusy(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isConnected(): Boolean {
|
||||||
|
// This is a workaround to prevent AAPS to trigger connects when we have no patch activated
|
||||||
|
return if (!isInitialized()) true else medtrumService?.isConnected ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isConnecting(): Boolean = medtrumService?.isConnecting ?: false
|
||||||
|
override fun isHandshakeInProgress(): Boolean = false
|
||||||
|
|
||||||
|
override fun finishHandshaking() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun connect(reason: String) {
|
||||||
|
if (isInitialized()) {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "Medtrum connect - reason:$reason")
|
||||||
|
if (medtrumService != null) {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "Medtrum connect - Attempt connection!")
|
||||||
|
val success = medtrumService?.connect(reason) ?: false
|
||||||
|
if (!success) ToastUtils.errorToast(context, info.nightscout.core.ui.R.string.ble_not_supported_or_not_paired)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun disconnect(reason: String) {
|
||||||
|
if (isInitialized()) {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "Medtrum disconnect from: $reason")
|
||||||
|
medtrumService?.disconnect(reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stopConnecting() {
|
||||||
|
if (isInitialized()) {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "Medtrum stopConnecting")
|
||||||
|
medtrumService?.stopConnecting()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPumpStatus(reason: String) {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "Medtrum getPumpStatus - reason:$reason")
|
||||||
|
if (isInitialized()) {
|
||||||
|
val connectionOK = medtrumService?.readPumpStatus() ?: false
|
||||||
|
if (connectionOK == false) {
|
||||||
|
aapsLogger.error(LTag.PUMP, "Medtrum getPumpStatus failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setNewBasalProfile(profile: Profile): PumpEnactResult {
|
||||||
|
// New profile will be set when patch is activated
|
||||||
|
if (!isInitialized()) return PumpEnactResult(injector).success(true).enacted(true)
|
||||||
|
|
||||||
|
return if (medtrumService?.updateBasalsInPump(profile) == true) {
|
||||||
|
rxBus.send(EventDismissNotification(Notification.FAILED_UPDATE_PROFILE))
|
||||||
|
uiInteraction.addNotificationValidFor(Notification.PROFILE_SET_OK, rh.gs(info.nightscout.core.ui.R.string.profile_set_ok), Notification.INFO, 60)
|
||||||
|
PumpEnactResult(injector).success(true).enacted(true)
|
||||||
|
} else {
|
||||||
|
uiInteraction.addNotification(Notification.FAILED_UPDATE_PROFILE, rh.gs(info.nightscout.core.ui.R.string.failed_update_basal_profile), Notification.URGENT)
|
||||||
|
PumpEnactResult(injector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isThisProfileSet(profile: Profile): Boolean {
|
||||||
|
if (!isInitialized()) return true
|
||||||
|
var result = false
|
||||||
|
val profileBytes = medtrumPump.buildMedtrumProfileArray(profile)
|
||||||
|
if (profileBytes?.size == medtrumPump.actualBasalProfile.size) {
|
||||||
|
result = true
|
||||||
|
for (i in profileBytes.indices) {
|
||||||
|
if (profileBytes[i] != medtrumPump.actualBasalProfile[i]) {
|
||||||
|
result = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun lastDataTime(): Long = medtrumPump.lastConnection
|
||||||
|
override val baseBasalRate: Double
|
||||||
|
get() = medtrumPump.baseBasalRate
|
||||||
|
|
||||||
|
override val reservoirLevel: Double
|
||||||
|
get() = medtrumPump.reservoir
|
||||||
|
|
||||||
|
override val batteryLevel: Int
|
||||||
|
get() = 0 // We cannot determine battery level (yet)
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun deliverTreatment(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "deliverTreatment: " + detailedBolusInfo.insulin + "U")
|
||||||
|
if (!isInitialized()) return PumpEnactResult(injector).success(false).enacted(false)
|
||||||
|
detailedBolusInfo.insulin = constraintChecker.applyBolusConstraints(Constraint(detailedBolusInfo.insulin)).value()
|
||||||
|
return if (detailedBolusInfo.insulin > 0 && detailedBolusInfo.carbs == 0.0) {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "deliverTreatment: Delivering bolus: " + detailedBolusInfo.insulin + "U")
|
||||||
|
val t = EventOverviewBolusProgress.Treatment(0.0, 0, detailedBolusInfo.bolusType == DetailedBolusInfo.BolusType.SMB, detailedBolusInfo.id)
|
||||||
|
val connectionOK = medtrumService?.setBolus(detailedBolusInfo, t) ?: false
|
||||||
|
val result = PumpEnactResult(injector)
|
||||||
|
result.success = connectionOK && abs(detailedBolusInfo.insulin - t.insulin) < pumpDescription.bolusStep
|
||||||
|
result.bolusDelivered = t.insulin
|
||||||
|
if (!result.success) {
|
||||||
|
// Note: There are no error codes
|
||||||
|
result.comment = "failed"
|
||||||
|
} else {
|
||||||
|
result.comment = "ok"
|
||||||
|
}
|
||||||
|
aapsLogger.debug(LTag.PUMP, "deliverTreatment: OK. Success: ${result.success} Asked: ${detailedBolusInfo.insulin} Delivered: ${result.bolusDelivered}")
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "deliverTreatment: Invalid input")
|
||||||
|
val result = PumpEnactResult(injector)
|
||||||
|
result.success = false
|
||||||
|
result.bolusDelivered = 0.0
|
||||||
|
result.comment = rh.gs(info.nightscout.core.ui.R.string.invalid_input)
|
||||||
|
aapsLogger.error("deliverTreatment: Invalid input")
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stopBolusDelivering() {
|
||||||
|
if (!isInitialized()) return
|
||||||
|
|
||||||
|
aapsLogger.info(LTag.PUMP, "stopBolusDelivering")
|
||||||
|
medtrumService?.stopBolus()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun setTempBasalAbsolute(absoluteRate: Double, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: PumpSync.TemporaryBasalType): PumpEnactResult {
|
||||||
|
if (!isInitialized()) return PumpEnactResult(injector).success(false).enacted(false)
|
||||||
|
|
||||||
|
aapsLogger.info(LTag.PUMP, "setTempBasalAbsolute - absoluteRate: $absoluteRate, durationInMinutes: $durationInMinutes, enforceNew: $enforceNew")
|
||||||
|
// round rate to pump rate
|
||||||
|
val pumpRate = constraintChecker.applyBasalConstraints(Constraint(absoluteRate), profile).value()
|
||||||
|
temporaryBasalStorage.add(PumpSync.PumpState.TemporaryBasal(dateUtil.now(), T.mins(durationInMinutes.toLong()).msecs(), pumpRate, true, tbrType, 0L, 0L))
|
||||||
|
val connectionOK = medtrumService?.setTempBasal(pumpRate, durationInMinutes) ?: false
|
||||||
|
if (connectionOK
|
||||||
|
&& medtrumPump.tempBasalInProgress
|
||||||
|
&& Math.abs(medtrumPump.tempBasalAbsoluteRate - pumpRate) <= 0.05
|
||||||
|
) {
|
||||||
|
|
||||||
|
return PumpEnactResult(injector).success(true).enacted(true).duration(durationInMinutes).absolute(medtrumPump.tempBasalAbsoluteRate)
|
||||||
|
.isPercent(false)
|
||||||
|
.isTempCancel(false)
|
||||||
|
} else {
|
||||||
|
aapsLogger.error(
|
||||||
|
LTag.PUMP,
|
||||||
|
"setTempBasalAbsolute failed, connectionOK: $connectionOK, tempBasalInProgress: ${medtrumPump.tempBasalInProgress}, tempBasalAbsoluteRate: ${medtrumPump.tempBasalAbsoluteRate}"
|
||||||
|
)
|
||||||
|
return PumpEnactResult(injector).success(false).enacted(false).comment("Medtrum setTempBasalAbsolute failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
return PumpEnactResult(injector).success(false).enacted(false).comment("Medtrum driver does not support percentage temp basals")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setExtendedBolus(insulin: Double, durationInMinutes: Int): PumpEnactResult {
|
||||||
|
aapsLogger.info(LTag.PUMP, "setExtendedBolus - insulin: $insulin, durationInMinutes: $durationInMinutes")
|
||||||
|
return PumpEnactResult(injector).success(false).enacted(false).comment("Medtrum driver does not support extended boluses")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cancelTempBasal(enforceNew: Boolean): PumpEnactResult {
|
||||||
|
if (!isInitialized()) return PumpEnactResult(injector).success(false).enacted(false)
|
||||||
|
|
||||||
|
aapsLogger.info(LTag.PUMP, "cancelTempBasal - enforceNew: $enforceNew")
|
||||||
|
val connectionOK = medtrumService?.cancelTempBasal() ?: false
|
||||||
|
if (connectionOK && !medtrumPump.tempBasalInProgress) {
|
||||||
|
return PumpEnactResult(injector).success(true).enacted(true).isTempCancel(true)
|
||||||
|
} else {
|
||||||
|
aapsLogger.error(LTag.PUMP, "cancelTempBasal failed, connectionOK: $connectionOK, tempBasalInProgress: ${medtrumPump.tempBasalInProgress}")
|
||||||
|
return PumpEnactResult(injector).success(false).enacted(false).comment("Medtrum cancelTempBasal failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cancelExtendedBolus(): PumpEnactResult {
|
||||||
|
return PumpEnactResult(injector)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getJSONStatus(profile: Profile, profileName: String, version: String): JSONObject {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
if (medtrumPump.lastConnection + 60 * 60 * 1000L < System.currentTimeMillis()) {
|
||||||
|
return JSONObject()
|
||||||
|
}
|
||||||
|
val pumpJson = JSONObject()
|
||||||
|
val status = JSONObject()
|
||||||
|
val extended = JSONObject()
|
||||||
|
try {
|
||||||
|
status.put(
|
||||||
|
"status", if (!isSuspended()) "normal"
|
||||||
|
else if (isInitialized() && isSuspended()) "suspended"
|
||||||
|
else "no active patch"
|
||||||
|
)
|
||||||
|
status.put("timestamp", dateUtil.toISOString(medtrumPump.lastConnection))
|
||||||
|
if (medtrumPump.lastBolusTime != 0L) {
|
||||||
|
extended.put("lastBolus", dateUtil.dateAndTimeString(medtrumPump.lastBolusTime))
|
||||||
|
extended.put("lastBolusAmount", medtrumPump.lastBolusAmount)
|
||||||
|
}
|
||||||
|
val tb = pumpSync.expectedPumpState().temporaryBasal
|
||||||
|
if (tb != null) {
|
||||||
|
extended.put("TempBasalAbsoluteRate", tb.convertedToAbsolute(now, profile))
|
||||||
|
extended.put("TempBasalStart", dateUtil.dateAndTimeString(tb.timestamp))
|
||||||
|
extended.put("TempBasalRemaining", tb.plannedRemainingMinutes)
|
||||||
|
}
|
||||||
|
extended.put("BaseBasalRate", baseBasalRate)
|
||||||
|
try {
|
||||||
|
extended.put("ActiveProfile", profileName)
|
||||||
|
} catch (ignored: Exception) {
|
||||||
|
}
|
||||||
|
pumpJson.put("status", status)
|
||||||
|
pumpJson.put("extended", extended)
|
||||||
|
pumpJson.put("reservoir", medtrumPump.reservoir.toInt())
|
||||||
|
pumpJson.put("clock", dateUtil.toISOString(now))
|
||||||
|
} catch (e: JSONException) {
|
||||||
|
aapsLogger.error(LTag.PUMP, "Unhandled exception: $e")
|
||||||
|
}
|
||||||
|
return pumpJson
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun manufacturer(): ManufacturerType {
|
||||||
|
return ManufacturerType.Medtrum
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun model(): PumpType {
|
||||||
|
return medtrumPump.pumpType()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialNumber(): String {
|
||||||
|
// Load from SP here, because this value will be get before pump is initialized
|
||||||
|
return medtrumPump.pumpSNFromSP.toString(radix = 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val pumpDescription: PumpDescription
|
||||||
|
get() = PumpDescription(medtrumPump.pumpType())
|
||||||
|
|
||||||
|
override fun shortStatus(veryShort: Boolean): String {
|
||||||
|
var ret = ""
|
||||||
|
if (medtrumPump.lastConnection != 0L) {
|
||||||
|
val agoMillis = System.currentTimeMillis() - medtrumPump.lastConnection
|
||||||
|
val agoMin = (agoMillis / 60.0 / 1000.0).toInt()
|
||||||
|
ret += "LastConn: $agoMin minAgo\n"
|
||||||
|
}
|
||||||
|
if (medtrumPump.lastBolusTime != 0L)
|
||||||
|
ret += "LastBolus: ${DecimalFormatter.to2Decimal(medtrumPump.lastBolusAmount)}U @${DateFormat.format("HH:mm", medtrumPump.lastBolusTime)}\n"
|
||||||
|
|
||||||
|
if (medtrumPump.tempBasalInProgress)
|
||||||
|
ret += "Temp: ${medtrumPump.temporaryBasalToString()}\n"
|
||||||
|
|
||||||
|
ret += "Res: ${DecimalFormatter.to0Decimal(medtrumPump.reservoir)}U\n"
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isFakingTempsByExtendedBoluses: Boolean = false
|
||||||
|
|
||||||
|
override fun loadTDDs(): PumpEnactResult {
|
||||||
|
return PumpEnactResult(injector) // Note: Can implement this if we implement history fully (no priority)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCustomActions(): List<CustomAction>? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun executeCustomAction(customActionType: CustomActionType) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun executeCustomCommand(customCommand: CustomCommand): PumpEnactResult? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canHandleDST(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun timezoneOrDSTChanged(timeChangeType: TimeChangeType) {
|
||||||
|
medtrumPump.needCheckTimeUpdate = true
|
||||||
|
if (isInitialized()) {
|
||||||
|
commandQueue.updateTime(object : Callback() {
|
||||||
|
override fun run() {
|
||||||
|
if (this.result.success == false) {
|
||||||
|
aapsLogger.error(LTag.PUMP, "Medtrum time update failed")
|
||||||
|
// Only notify here on failure (connection may be failed), service will handle success
|
||||||
|
medtrumService?.timeUpdateNotification(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Medtrum interface
|
||||||
|
override fun loadEvents(): PumpEnactResult {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
val result = PumpEnactResult(injector).success(false)
|
||||||
|
result.comment = "pump not initialized"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
val connectionOK = medtrumService?.loadEvents() ?: false
|
||||||
|
return PumpEnactResult(injector).success(connectionOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setUserOptions(): PumpEnactResult {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
val result = PumpEnactResult(injector).success(false)
|
||||||
|
result.comment = "pump not initialized"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
val connectionOK = medtrumService?.setUserSettings() ?: false
|
||||||
|
return PumpEnactResult(injector).success(connectionOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearAlarms(): PumpEnactResult {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
val result = PumpEnactResult(injector).success(false)
|
||||||
|
result.comment = "pump not initialized"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
val connectionOK = medtrumService?.clearAlarms() ?: false
|
||||||
|
return PumpEnactResult(injector).success(connectionOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deactivate(): PumpEnactResult {
|
||||||
|
val connectionOK = medtrumService?.deactivatePatch() ?: false
|
||||||
|
return PumpEnactResult(injector).success(connectionOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateTime(): PumpEnactResult {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
val result = PumpEnactResult(injector).success(false)
|
||||||
|
result.comment = "pump not initialized"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
val connectionOK = medtrumService?.updateTimeIfNeeded() ?: false
|
||||||
|
return PumpEnactResult(injector).success(connectionOK)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,539 @@
|
||||||
|
package info.nightscout.pump.medtrum
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
|
import info.nightscout.interfaces.profile.Profile
|
||||||
|
import info.nightscout.interfaces.pump.PumpSync
|
||||||
|
import info.nightscout.interfaces.pump.TemporaryBasalStorage
|
||||||
|
import info.nightscout.interfaces.pump.defs.PumpType
|
||||||
|
import info.nightscout.pump.medtrum.code.ConnectionState
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.AlarmSetting
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.AlarmState
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.BasalType
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
|
||||||
|
import info.nightscout.pump.medtrum.extension.toByteArray
|
||||||
|
import info.nightscout.pump.medtrum.extension.toInt
|
||||||
|
import info.nightscout.rx.events.EventOverviewBolusProgress
|
||||||
|
import info.nightscout.rx.logging.AAPSLogger
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import info.nightscout.shared.interfaces.ResourceHelper
|
||||||
|
import info.nightscout.shared.sharedPreferences.SP
|
||||||
|
import info.nightscout.shared.utils.DateUtil
|
||||||
|
import info.nightscout.shared.utils.T
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
import kotlin.math.round
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class MedtrumPump @Inject constructor(
|
||||||
|
private val aapsLogger: AAPSLogger,
|
||||||
|
private val rh: ResourceHelper,
|
||||||
|
private val sp: SP,
|
||||||
|
private val dateUtil: DateUtil,
|
||||||
|
private val pumpSync: PumpSync,
|
||||||
|
private val temporaryBasalStorage: TemporaryBasalStorage
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val FAKE_TBR_LENGTH = 4800L
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection state flow
|
||||||
|
private val _connectionState = MutableStateFlow(ConnectionState.DISCONNECTED)
|
||||||
|
val connectionStateFlow: StateFlow<ConnectionState> = _connectionState
|
||||||
|
var connectionState: ConnectionState
|
||||||
|
get() = _connectionState.value
|
||||||
|
set(value) {
|
||||||
|
_connectionState.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pump state flow
|
||||||
|
private val _pumpState = MutableStateFlow(MedtrumPumpState.NONE)
|
||||||
|
val pumpStateFlow: StateFlow<MedtrumPumpState> = _pumpState
|
||||||
|
var pumpState: MedtrumPumpState
|
||||||
|
get() = _pumpState.value
|
||||||
|
set(value) {
|
||||||
|
_pumpState.value = value
|
||||||
|
sp.putInt(R.string.key_pump_state, value.state.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Active alarms
|
||||||
|
private var _activeAlarms: EnumSet<AlarmState> = EnumSet.noneOf(AlarmState::class.java)
|
||||||
|
var activeAlarms: EnumSet<AlarmState>
|
||||||
|
get() = _activeAlarms
|
||||||
|
set(value) {
|
||||||
|
_activeAlarms = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prime progress as state flow
|
||||||
|
private val _primeProgress = MutableStateFlow(0)
|
||||||
|
val primeProgressFlow: StateFlow<Int> = _primeProgress
|
||||||
|
var primeProgress: Int
|
||||||
|
get() = _primeProgress.value
|
||||||
|
set(value) {
|
||||||
|
_primeProgress.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _lastBasalType: MutableStateFlow<BasalType> = MutableStateFlow(BasalType.NONE)
|
||||||
|
val lastBasalTypeFlow: StateFlow<BasalType> = _lastBasalType
|
||||||
|
val lastBasalType: BasalType
|
||||||
|
get() = _lastBasalType.value
|
||||||
|
|
||||||
|
private val _lastBasalRate = MutableStateFlow(0.0)
|
||||||
|
val lastBasalRateFlow: StateFlow<Double> = _lastBasalRate
|
||||||
|
val lastBasalRate: Double
|
||||||
|
get() = _lastBasalRate.value
|
||||||
|
|
||||||
|
private val _reservoir = MutableStateFlow(0.0)
|
||||||
|
val reservoirFlow: StateFlow<Double> = _reservoir
|
||||||
|
var reservoir: Double
|
||||||
|
get() = _reservoir.value
|
||||||
|
set(value) {
|
||||||
|
_reservoir.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var batteryVoltage_A = 0.0 // Not used in UI
|
||||||
|
private val _batteryVoltage_B = MutableStateFlow(0.0)
|
||||||
|
val batteryVoltage_BFlow: StateFlow<Double> = _batteryVoltage_B
|
||||||
|
var batteryVoltage_B: Double
|
||||||
|
get() = _batteryVoltage_B.value
|
||||||
|
set(value) {
|
||||||
|
_batteryVoltage_B.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stuff stored in SP */
|
||||||
|
private var _patchSessionToken = 0L
|
||||||
|
var patchSessionToken: Long
|
||||||
|
get() = _patchSessionToken
|
||||||
|
set(value) {
|
||||||
|
_patchSessionToken = value
|
||||||
|
sp.putLong(R.string.key_session_token, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _patchId = 0L
|
||||||
|
var patchId: Long
|
||||||
|
get() = _patchId
|
||||||
|
set(value) {
|
||||||
|
_patchId = value
|
||||||
|
sp.putLong(R.string.key_patch_id, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _currentSequenceNumber = 0
|
||||||
|
var currentSequenceNumber: Int
|
||||||
|
get() = _currentSequenceNumber
|
||||||
|
set(value) {
|
||||||
|
_currentSequenceNumber = value
|
||||||
|
sp.putInt(R.string.key_current_sequence_number, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _syncedSequenceNumber = 0
|
||||||
|
var syncedSequenceNumber: Int
|
||||||
|
get() = _syncedSequenceNumber
|
||||||
|
set(value) {
|
||||||
|
_syncedSequenceNumber = value
|
||||||
|
sp.putInt(R.string.key_synced_sequence_number, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _actualBasalProfile = byteArrayOf(0)
|
||||||
|
var actualBasalProfile: ByteArray
|
||||||
|
get() = _actualBasalProfile
|
||||||
|
set(value) {
|
||||||
|
_actualBasalProfile = value
|
||||||
|
val encodedString = Base64.encodeToString(value, Base64.DEFAULT)
|
||||||
|
sp.putString(R.string.key_actual_basal_profile, encodedString ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _lastBolusTime = 0L // Time in ms!
|
||||||
|
var lastBolusTime: Long
|
||||||
|
get() = _lastBolusTime
|
||||||
|
set(value) {
|
||||||
|
_lastBolusTime = value
|
||||||
|
sp.putLong(R.string.key_last_bolus_time, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _lastBolusAmount = 0.0
|
||||||
|
var lastBolusAmount: Double
|
||||||
|
get() = _lastBolusAmount
|
||||||
|
set(value) {
|
||||||
|
_lastBolusAmount = value
|
||||||
|
sp.putDouble(R.string.key_last_bolus_amount, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _lastConnection = 0L // Time in ms!
|
||||||
|
var lastConnection: Long
|
||||||
|
get() = _lastConnection
|
||||||
|
set(value) {
|
||||||
|
_lastConnection = value
|
||||||
|
sp.putLong(R.string.key_last_connection, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _deviceType: Int = 80 // As reported by pump
|
||||||
|
var deviceType: Int
|
||||||
|
get() = _deviceType
|
||||||
|
set(value) {
|
||||||
|
_deviceType = value
|
||||||
|
sp.putInt(R.string.key_device_type, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _swVersion: String = "" // As reported by pump
|
||||||
|
var swVersion: String
|
||||||
|
get() = _swVersion
|
||||||
|
set(value) {
|
||||||
|
_swVersion = value
|
||||||
|
sp.putString(R.string.key_sw_version, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _patchStartTime = 0L // Time in ms!
|
||||||
|
var patchStartTime: Long
|
||||||
|
get() = _patchStartTime
|
||||||
|
set(value) {
|
||||||
|
_patchStartTime = value
|
||||||
|
sp.putLong(R.string.key_patch_start_time, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _pumpTimeZoneOffset = 0 // As reported by pump
|
||||||
|
var pumpTimeZoneOffset: Int
|
||||||
|
get() = _pumpTimeZoneOffset
|
||||||
|
set(value) {
|
||||||
|
_pumpTimeZoneOffset = value
|
||||||
|
sp.putInt(R.string.key_pump_time_zone_offset, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _pumpSN = 0L
|
||||||
|
val pumpSN: Long
|
||||||
|
get() = _pumpSN
|
||||||
|
|
||||||
|
val pumpSNFromSP: Long
|
||||||
|
get() =
|
||||||
|
try {
|
||||||
|
sp.getString(R.string.key_sn_input, "0").toLong(radix = 16)
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "pumpSNFromSP: Invalid input!")
|
||||||
|
0L
|
||||||
|
}
|
||||||
|
|
||||||
|
var needCheckTimeUpdate = false
|
||||||
|
var lastTimeReceivedFromPump = 0L // Time in ms!
|
||||||
|
var suspendTime = 0L // Time in ms!
|
||||||
|
var patchAge = 0L // Time in seconds?! // As reported by pump, not used (yet)
|
||||||
|
|
||||||
|
// bolus status
|
||||||
|
var bolusingTreatment: EventOverviewBolusProgress.Treatment? = null // actually delivered treatment
|
||||||
|
var bolusAmountToBeDelivered = 0.0 // amount to be delivered
|
||||||
|
var bolusProgressLastTimeStamp: Long = 0 // timestamp of last bolus progress message
|
||||||
|
var bolusStopped = false // bolus stopped by user
|
||||||
|
var bolusDone = false // Bolus completed or stopped on pump
|
||||||
|
|
||||||
|
// Last basal status update (from pump)
|
||||||
|
private var _lastBasalSequence = 0
|
||||||
|
val lastBasalSequence: Int
|
||||||
|
get() = _lastBasalSequence
|
||||||
|
|
||||||
|
private var _lastBasalPatchId = 0L
|
||||||
|
val lastBasalPatchId: Long
|
||||||
|
get() = _lastBasalPatchId
|
||||||
|
|
||||||
|
private var _lastBasalStartTime = 0L
|
||||||
|
val lastBasalStartTime: Long
|
||||||
|
get() = _lastBasalStartTime
|
||||||
|
|
||||||
|
val baseBasalRate: Double
|
||||||
|
get() = getHourlyBasalFromMedtrumProfileArray(actualBasalProfile, dateUtil.now())
|
||||||
|
|
||||||
|
// TBR status
|
||||||
|
val tempBasalInProgress: Boolean
|
||||||
|
get() = lastBasalType == BasalType.ABSOLUTE_TEMP || lastBasalType == BasalType.RELATIVE_TEMP
|
||||||
|
val tempBasalAbsoluteRate: Double
|
||||||
|
get() = if (tempBasalInProgress) lastBasalRate else 0.0
|
||||||
|
|
||||||
|
// Last stop status update
|
||||||
|
var lastStopSequence = 0
|
||||||
|
var lastStopPatchId = 0L
|
||||||
|
|
||||||
|
// User settings (desired values, to be set on pump)
|
||||||
|
var desiredPatchExpiration = false
|
||||||
|
var desiredAlarmSetting = AlarmSetting.LIGHT_VIBRATE_AND_BEEP
|
||||||
|
var desiredHourlyMaxInsulin: Int = 40
|
||||||
|
var desiredDailyMaxInsulin: Int = 180
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Load stuff from SP
|
||||||
|
_patchSessionToken = sp.getLong(R.string.key_session_token, 0L)
|
||||||
|
_lastConnection = sp.getLong(R.string.key_last_connection, 0L)
|
||||||
|
_lastBolusTime = sp.getLong(R.string.key_last_bolus_time, 0L)
|
||||||
|
_lastBolusAmount = sp.getDouble(R.string.key_last_bolus_amount, 0.0)
|
||||||
|
_currentSequenceNumber = sp.getInt(R.string.key_current_sequence_number, 0)
|
||||||
|
_patchId = sp.getLong(R.string.key_patch_id, 0L)
|
||||||
|
_syncedSequenceNumber = sp.getInt(R.string.key_synced_sequence_number, 0)
|
||||||
|
_pumpState.value = MedtrumPumpState.fromByte(sp.getInt(R.string.key_pump_state, MedtrumPumpState.NONE.state.toInt()).toByte())
|
||||||
|
_deviceType = sp.getInt(R.string.key_device_type, 0)
|
||||||
|
_swVersion = sp.getString(R.string.key_sw_version, "")
|
||||||
|
_patchStartTime = sp.getLong(R.string.key_patch_start_time, 0L)
|
||||||
|
_pumpTimeZoneOffset = sp.getInt(R.string.key_pump_time_zone_offset, 0)
|
||||||
|
|
||||||
|
loadActiveAlarms()
|
||||||
|
|
||||||
|
val encodedString = sp.getString(R.string.key_actual_basal_profile, "0")
|
||||||
|
try {
|
||||||
|
_actualBasalProfile = Base64.decode(encodedString, Base64.DEFAULT)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
aapsLogger.error(LTag.PUMP, "Error decoding basal profile from SP: $encodedString")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pumpType(): PumpType =
|
||||||
|
when (deviceType) {
|
||||||
|
80, 88 -> PumpType.MEDTRUM_NANO
|
||||||
|
else -> PumpType.MEDTRUM_UNTESTED
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadUserSettingsFromSP() {
|
||||||
|
desiredPatchExpiration = sp.getBoolean(info.nightscout.pump.medtrum.R.string.key_patch_expiration, false)
|
||||||
|
val alarmSettingCode = sp.getString(info.nightscout.pump.medtrum.R.string.key_alarm_setting, AlarmSetting.LIGHT_VIBRATE_AND_BEEP.code.toString()).toByte()
|
||||||
|
desiredAlarmSetting = AlarmSetting.values().firstOrNull { it.code == alarmSettingCode } ?: AlarmSetting.LIGHT_VIBRATE_AND_BEEP
|
||||||
|
desiredHourlyMaxInsulin = sp.getInt(info.nightscout.pump.medtrum.R.string.key_hourly_max_insulin, 40)
|
||||||
|
desiredDailyMaxInsulin = sp.getInt(info.nightscout.pump.medtrum.R.string.key_daily_max_insulin, 180)
|
||||||
|
_pumpSN = pumpSNFromSP
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildMedtrumProfileArray(nsProfile: Profile): ByteArray? {
|
||||||
|
val list = nsProfile.getBasalValues()
|
||||||
|
var basals = byteArrayOf()
|
||||||
|
for (item in list) {
|
||||||
|
val rate = round(item.value / 0.05).toInt()
|
||||||
|
val time = item.timeAsSeconds / 60
|
||||||
|
if (rate > 0xFFF || time > 0xFFF) {
|
||||||
|
aapsLogger.error(LTag.PUMP, "buildMedtrumProfileArray: rate or time too large: $rate, $time")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
basals += ((rate shl 12) + time).toByteArray(3)
|
||||||
|
aapsLogger.debug(LTag.PUMP, "buildMedtrumProfileArray: value: ${item.value} time: ${item.timeAsSeconds}, converted: $rate, $time")
|
||||||
|
}
|
||||||
|
return (list.size).toByteArray(1) + basals
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getHourlyBasalFromMedtrumProfileArray(basalProfile: ByteArray, timestamp: Long): Double {
|
||||||
|
val basalCount = basalProfile[0].toInt()
|
||||||
|
var basal = 0.0
|
||||||
|
if (basalProfile.size < 4 || (basalProfile.size - 1) % 3 != 0 || basalCount > 24) {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "getHourlyBasalFromMedtrumProfileArray: No valid basal profile set")
|
||||||
|
return basal
|
||||||
|
}
|
||||||
|
|
||||||
|
val date = GregorianCalendar()
|
||||||
|
date.timeInMillis = timestamp
|
||||||
|
val hourOfDayMinutes = date.get(GregorianCalendar.HOUR_OF_DAY) * 60 + date.get(GregorianCalendar.MINUTE)
|
||||||
|
|
||||||
|
for (index in 0 until basalCount) {
|
||||||
|
val currentIndex = 1 + (index * 3)
|
||||||
|
val nextIndex = currentIndex + 3
|
||||||
|
val rateAndTime = basalProfile.copyOfRange(currentIndex, nextIndex).toInt()
|
||||||
|
val rate = (rateAndTime shr 12) * 0.05
|
||||||
|
val startMinutes = rateAndTime and 0xFFF
|
||||||
|
|
||||||
|
val endMinutes = if (nextIndex < basalProfile.size) {
|
||||||
|
val nextRateAndTime = basalProfile.copyOfRange(nextIndex, nextIndex + 3).toInt()
|
||||||
|
nextRateAndTime and 0xFFF
|
||||||
|
} else {
|
||||||
|
24 * 60
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hourOfDayMinutes in startMinutes until endMinutes) {
|
||||||
|
basal = rate
|
||||||
|
aapsLogger.debug(LTag.PUMP, "getHourlyBasalFromMedtrumProfileArray: basal: $basal")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// aapsLogger.debug(LTag.PUMP, "getHourlyBasalFromMedtrumProfileArray: rate: $rate, startMinutes: $startMinutes, endMinutes: $endMinutes")
|
||||||
|
}
|
||||||
|
return basal
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleBolusStatusUpdate(bolusType: Int, bolusCompleted: Boolean, amountDelivered: Double) {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "handleBolusStatusUpdate: bolusType: $bolusType bolusCompleted: $bolusCompleted amountDelivered: $amountDelivered")
|
||||||
|
bolusProgressLastTimeStamp = dateUtil.now()
|
||||||
|
bolusingTreatment?.insulin = amountDelivered
|
||||||
|
bolusDone = bolusCompleted
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleBasalStatusUpdate(basalType: BasalType, basalValue: Double, basalSequence: Int, basalPatchId: Long, basalStartTime: Long) {
|
||||||
|
handleBasalStatusUpdate(basalType, basalValue, basalSequence, basalPatchId, basalStartTime, dateUtil.now())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleBasalStatusUpdate(basalType: BasalType, basalRate: Double, basalSequence: Int, basalPatchId: Long, basalStartTime: Long, receivedTime: Long) {
|
||||||
|
aapsLogger.debug(
|
||||||
|
LTag.PUMP,
|
||||||
|
"handleBasalStatusUpdate: basalType: $basalType basalValue: $basalRate basalSequence: $basalSequence basalPatchId: $basalPatchId basalStartTime: $basalStartTime " + "receivedTime: $receivedTime"
|
||||||
|
)
|
||||||
|
@Suppress("UNNECESSARY_SAFE_CALL") // Safe call to allow mocks to return null
|
||||||
|
val expectedTemporaryBasal = pumpSync.expectedPumpState()?.temporaryBasal
|
||||||
|
if (basalType.isTempBasal() && expectedTemporaryBasal?.pumpId != basalStartTime) {
|
||||||
|
// Note: temporaryBasalInfo will be removed from temporaryBasalStorage after this call
|
||||||
|
val temporaryBasalInfo = temporaryBasalStorage.findTemporaryBasal(basalStartTime, basalRate)
|
||||||
|
|
||||||
|
// If duration is unknown, no way to get it now, set patch lifetime as duration
|
||||||
|
val duration = temporaryBasalInfo?.duration ?: T.mins(FAKE_TBR_LENGTH).msecs()
|
||||||
|
val adjustedBasalRate = if (basalType == BasalType.ABSOLUTE_TEMP) {
|
||||||
|
basalRate
|
||||||
|
} else {
|
||||||
|
(basalRate / baseBasalRate) * 100 // calculate the percentage of the original basal rate
|
||||||
|
}
|
||||||
|
val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
|
||||||
|
timestamp = basalStartTime,
|
||||||
|
rate = adjustedBasalRate,
|
||||||
|
duration = duration,
|
||||||
|
isAbsolute = (basalType == BasalType.ABSOLUTE_TEMP),
|
||||||
|
type = temporaryBasalInfo?.type,
|
||||||
|
pumpId = basalStartTime,
|
||||||
|
pumpType = pumpType(),
|
||||||
|
pumpSerial = pumpSN.toString(radix = 16)
|
||||||
|
)
|
||||||
|
aapsLogger.debug(
|
||||||
|
LTag.PUMPCOMM,
|
||||||
|
"handleBasalStatusUpdate: ${if (newRecord) "**NEW** " else ""}EVENT TEMP_START ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) " + "Rate: $basalRate Duration: ${duration} temporaryBasalInfo: $temporaryBasalInfo, expectedTemporaryBasal: $expectedTemporaryBasal"
|
||||||
|
)
|
||||||
|
} else if (basalType.isSuspendedByPump() && expectedTemporaryBasal?.pumpId != basalStartTime) {
|
||||||
|
val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
|
||||||
|
timestamp = basalStartTime,
|
||||||
|
rate = 0.0,
|
||||||
|
duration = T.mins(FAKE_TBR_LENGTH).msecs(),
|
||||||
|
isAbsolute = true,
|
||||||
|
type = PumpSync.TemporaryBasalType.PUMP_SUSPEND,
|
||||||
|
pumpId = basalStartTime,
|
||||||
|
pumpType = pumpType(),
|
||||||
|
pumpSerial = pumpSN.toString(radix = 16)
|
||||||
|
)
|
||||||
|
aapsLogger.debug(
|
||||||
|
LTag.PUMPCOMM,
|
||||||
|
"handleBasalStatusUpdate: ${if (newRecord) "**NEW** " else ""}EVENT TEMP_START ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) expectedTemporaryBasal: $expectedTemporaryBasal"
|
||||||
|
)
|
||||||
|
} else if (basalType == BasalType.NONE && expectedTemporaryBasal?.rate != basalRate && expectedTemporaryBasal?.duration != T.mins(FAKE_TBR_LENGTH).msecs()) {
|
||||||
|
// Pump suspended, set fake TBR
|
||||||
|
setFakeTBR()
|
||||||
|
} else if (basalType == BasalType.STANDARD) {
|
||||||
|
if (expectedTemporaryBasal != null) {
|
||||||
|
// Pump resumed, sync end
|
||||||
|
val success = pumpSync.syncStopTemporaryBasalWithPumpId(
|
||||||
|
timestamp = basalStartTime + 250, // Time of normal basal start = time of tbr end
|
||||||
|
endPumpId = basalStartTime + 250, // +250ms Make sure there is time between start and stop of TBR
|
||||||
|
pumpType = pumpType(),
|
||||||
|
pumpSerial = pumpSN.toString(radix = 16)
|
||||||
|
)
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "handleBasalStatusUpdate: EVENT TEMP_END ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) success: $success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update medtrum pump state
|
||||||
|
_lastBasalType.value = basalType
|
||||||
|
_lastBasalRate.value = basalRate
|
||||||
|
_lastBasalSequence = basalSequence
|
||||||
|
if (basalSequence > currentSequenceNumber) {
|
||||||
|
currentSequenceNumber = basalSequence
|
||||||
|
}
|
||||||
|
_lastBasalPatchId = basalPatchId
|
||||||
|
if (basalPatchId != patchId) {
|
||||||
|
aapsLogger.error(LTag.PUMP, "handleBasalStatusUpdate: WTF? PatchId in status update does not match current patchId!")
|
||||||
|
}
|
||||||
|
_lastBasalStartTime = basalStartTime
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleStopStatusUpdate(stopSequence: Int, stopPatchId: Long) {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "handleStopStatusUpdate: stopSequence: $stopSequence stopPatchId: $stopPatchId")
|
||||||
|
lastStopSequence = stopSequence
|
||||||
|
if (stopSequence > currentSequenceNumber) {
|
||||||
|
currentSequenceNumber = stopSequence
|
||||||
|
}
|
||||||
|
lastStopPatchId = stopPatchId
|
||||||
|
if (stopPatchId != patchId) {
|
||||||
|
aapsLogger.error(LTag.PUMP, "handleStopStatusUpdate: WTF? PatchId in status update does not match current patchId!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setFakeTBRIfNeeded() {
|
||||||
|
val expectedTemporaryBasal = pumpSync.expectedPumpState().temporaryBasal
|
||||||
|
if (expectedTemporaryBasal?.duration != T.mins(FAKE_TBR_LENGTH).msecs()) {
|
||||||
|
setFakeTBR()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setFakeTBR() {
|
||||||
|
val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
|
||||||
|
timestamp = dateUtil.now(),
|
||||||
|
rate = 0.0,
|
||||||
|
duration = T.mins(FAKE_TBR_LENGTH).msecs(),
|
||||||
|
isAbsolute = true,
|
||||||
|
type = PumpSync.TemporaryBasalType.PUMP_SUSPEND,
|
||||||
|
pumpId = dateUtil.now(),
|
||||||
|
pumpType = pumpType(),
|
||||||
|
pumpSerial = pumpSN.toString(radix = 16)
|
||||||
|
)
|
||||||
|
aapsLogger.debug(
|
||||||
|
LTag.PUMPCOMM,
|
||||||
|
"handleBasalStatusUpdate: ${if (newRecord) "**NEW** " else ""}EVENT TEMP_START (FAKE)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun temporaryBasalToString(): String {
|
||||||
|
if (!tempBasalInProgress) return ""
|
||||||
|
return tempBasalAbsoluteRate.toString() + "U/h"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addAlarm(alarm: AlarmState) {
|
||||||
|
activeAlarms.add(alarm)
|
||||||
|
saveActiveAlarms()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAlarm(alarm: AlarmState) {
|
||||||
|
activeAlarms.remove(alarm)
|
||||||
|
saveActiveAlarms()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearAlarmState() {
|
||||||
|
activeAlarms.clear()
|
||||||
|
saveActiveAlarms()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun alarmStateToString(alarmState: AlarmState): String {
|
||||||
|
val stringId = when (alarmState) {
|
||||||
|
AlarmState.NONE -> R.string.alarm_none
|
||||||
|
AlarmState.PUMP_LOW_BATTERY -> R.string.alarm_pump_low_battery
|
||||||
|
AlarmState.PUMP_LOW_RESERVOIR -> R.string.alarm_pump_low_reservoir
|
||||||
|
AlarmState.PUMP_EXPIRES_SOON -> R.string.alarm_pump_expires_soon
|
||||||
|
AlarmState.LOWBG_SUSPENDED -> R.string.alarm_lowbg_suspended
|
||||||
|
AlarmState.LOWBG_SUSPENDED2 -> R.string.alarm_lowbg_suspended2
|
||||||
|
AlarmState.AUTO_SUSPENDED -> R.string.alarm_auto_suspended
|
||||||
|
AlarmState.HMAX_SUSPENDED -> R.string.alarm_hmax_suspended
|
||||||
|
AlarmState.DMAX_SUSPENDED -> R.string.alarm_dmax_suspended
|
||||||
|
AlarmState.SUSPENDED -> R.string.alarm_suspended
|
||||||
|
AlarmState.PAUSED -> R.string.alarm_paused
|
||||||
|
AlarmState.OCCLUSION -> R.string.alarm_occlusion
|
||||||
|
AlarmState.EXPIRED -> R.string.alarm_expired
|
||||||
|
AlarmState.RESERVOIR_EMPTY -> R.string.alarm_reservoir_empty
|
||||||
|
AlarmState.PATCH_FAULT -> R.string.alarm_patch_fault
|
||||||
|
AlarmState.PATCH_FAULT2 -> R.string.alarm_patch_fault2
|
||||||
|
AlarmState.BASE_FAULT -> R.string.alarm_base_fault
|
||||||
|
AlarmState.BATTERY_OUT -> R.string.alarm_battery_out
|
||||||
|
AlarmState.NO_CALIBRATION -> R.string.alarm_no_calibration
|
||||||
|
}
|
||||||
|
return rh.gs(stringId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveActiveAlarms() {
|
||||||
|
val alarmsStr = activeAlarms.joinToString(separator = ",") { it.name }
|
||||||
|
sp.putString(R.string.key_active_alarms, alarmsStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadActiveAlarms() {
|
||||||
|
val alarmsStr = sp.getString(R.string.key_active_alarms, "")
|
||||||
|
if (alarmsStr.isNullOrEmpty()) {
|
||||||
|
activeAlarms = EnumSet.noneOf(AlarmState::class.java)
|
||||||
|
} else {
|
||||||
|
activeAlarms = alarmsStr.split(",")
|
||||||
|
.mapNotNull { AlarmState.values().find { alarm -> alarm.name == it } }
|
||||||
|
.let { EnumSet.copyOf(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package info.nightscout.pump.medtrum.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 {
|
||||||
|
// Set duplicate click prevention time
|
||||||
|
private const val MIN_CLICK_INTERVAL: Long = 1000
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package info.nightscout.pump.medtrum.bindingadapters
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.ColorRes
|
||||||
|
import androidx.databinding.BindingAdapter
|
||||||
|
import info.nightscout.pump.medtrum.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))
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package info.nightscout.pump.medtrum.code
|
||||||
|
|
||||||
|
enum class ConnectionState {
|
||||||
|
CONNECTED,
|
||||||
|
DISCONNECTED,
|
||||||
|
CONNECTING,
|
||||||
|
DISCONNECTING;
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package info.nightscout.pump.medtrum.code
|
||||||
|
|
||||||
|
enum class EventType {
|
||||||
|
CHANGE_PATCH_CLICKED,
|
||||||
|
PROFILE_NOT_SET,
|
||||||
|
;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package info.nightscout.pump.medtrum.code
|
||||||
|
|
||||||
|
enum class PatchStep {
|
||||||
|
START_DEACTIVATION,
|
||||||
|
DEACTIVATE,
|
||||||
|
FORCE_DEACTIVATION,
|
||||||
|
DEACTIVATION_COMPLETE,
|
||||||
|
PREPARE_PATCH,
|
||||||
|
PREPARE_PATCH_CONNECT,
|
||||||
|
PRIME,
|
||||||
|
PRIMING,
|
||||||
|
PRIME_COMPLETE,
|
||||||
|
ATTACH_PATCH,
|
||||||
|
ACTIVATE,
|
||||||
|
ACTIVATE_COMPLETE,
|
||||||
|
RETRY_ACTIVATION,
|
||||||
|
RETRY_ACTIVATION_CONNECT,
|
||||||
|
ERROR,
|
||||||
|
CANCEL,
|
||||||
|
COMPLETE;
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm
|
||||||
|
|
||||||
|
import kotlin.experimental.and
|
||||||
|
import info.nightscout.pump.medtrum.extension.toLong
|
||||||
|
|
||||||
|
class ManufacturerData(private val manufacturerDataBytes: ByteArray) {
|
||||||
|
private var deviceID: Long = 0
|
||||||
|
private var deviceType = 0
|
||||||
|
private var version = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
setData(manufacturerDataBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setData(inputData: ByteArray) {
|
||||||
|
var index = 0
|
||||||
|
val deviceIDBytes: ByteArray = inputData.copyOfRange(index, index + 4)
|
||||||
|
deviceID = deviceIDBytes.toLong()
|
||||||
|
index += 4
|
||||||
|
deviceType = (inputData[index] and 0xff.toByte()).toInt()
|
||||||
|
index += 1
|
||||||
|
version = (inputData[index] and 0xff.toByte()).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDeviceSN(): Long{
|
||||||
|
return deviceID
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDeviceType(): Int {
|
||||||
|
return deviceType
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getVersion(): Int {
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm
|
||||||
|
|
||||||
|
class ReadDataPacket(data: ByteArray) {
|
||||||
|
|
||||||
|
private var totalData = data.copyOfRange(0, data.size - 1) // Strip crc
|
||||||
|
private var dataSize: Byte = data[0]
|
||||||
|
|
||||||
|
fun addData(newData: ByteArray) {
|
||||||
|
totalData += newData.copyOfRange(4, newData.size - 1) // Strip header and crc
|
||||||
|
}
|
||||||
|
|
||||||
|
fun allDataReceived(): Boolean {
|
||||||
|
return (totalData.size >= dataSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getData(): ByteArray {
|
||||||
|
return totalData
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm
|
||||||
|
|
||||||
|
class WriteCommandPackets(data: ByteArray, sequenceNumber: Int) {
|
||||||
|
|
||||||
|
private val CRC_8_TABLE: IntArray = intArrayOf(0, 155, 173, 54, 193, 90, 108, 247, 25, 130, 180, 47, 216, 67, 117, 238, 50, 169, 159, 4, 243, 104, 94, 197, 43, 176, 134, 29, 234, 113, 71, 220, 100, 255, 201, 82, 165, 62, 8, 147, 125, 230, 208, 75, 188, 39, 17, 138, 86, 205, 251, 96, 151, 12, 58, 161, 79, 212, 226, 121, 142, 21, 35, 184, 200, 83, 101, 254, 9, 146, 164, 63, 209, 74, 124, 231, 16, 139, 189, 38, 250, 97, 87, 204, 59, 160, 150, 13, 227, 120, 78, 213, 34, 185, 143, 20, 172, 55, 1, 154, 109, 246, 192, 91, 181, 46, 24, 131, 116, 239, 217, 66, 158, 5, 51, 168, 95, 196, 242, 105, 135, 28, 42, 177, 70, 221, 235, 112, 11, 144, 166, 61, 202, 81, 103, 252, 18, 137, 191, 36, 211, 72, 126, 229, 57, 162, 148, 15, 248, 99, 85, 206, 32, 187, 141, 22, 225, 122, 76, 215, 111, 244, 194, 89, 174, 53, 3, 152, 118, 237, 219, 64, 183, 44, 26, 129, 93, 198, 240, 107, 156, 7, 49, 170, 68, 223, 233, 114, 133, 30, 40, 179, 195, 88, 110, 245, 2, 153, 175, 52, 218, 65, 119, 236, 27, 128, 182, 45, 241, 106, 92, 199, 48, 171, 157, 6, 232, 115, 69, 222, 41, 178, 132, 31, 167, 60, 10, 145, 102, 253, 203, 80, 190, 37, 19, 136, 127, 228, 210, 73, 149, 14, 56, 163, 84, 207, 249, 98, 140, 23, 33, 186, 77, 214, 224, 123)
|
||||||
|
|
||||||
|
private val packages = mutableListOf<ByteArray>()
|
||||||
|
private var index = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
// PackageIndex: 0 initially, if there are multiple packets, for the first packet it is set to 0 (not included in CRC calculation but sent in actual header)
|
||||||
|
var pkgIndex = 0
|
||||||
|
var header = byteArrayOf(
|
||||||
|
(data.size + 4).toByte(),
|
||||||
|
data[0],
|
||||||
|
sequenceNumber.toByte(),
|
||||||
|
pkgIndex.toByte()
|
||||||
|
)
|
||||||
|
|
||||||
|
var tmp: ByteArray = header + data.copyOfRange(1, data.size)
|
||||||
|
var totalCommand: ByteArray = tmp + calcCrc8(tmp, tmp.size).toByte()
|
||||||
|
|
||||||
|
if ((totalCommand.size - header.size) <= 15) {
|
||||||
|
packages.add(totalCommand + 0.toByte())
|
||||||
|
} else {
|
||||||
|
pkgIndex = 1
|
||||||
|
var remainingCommand = totalCommand.copyOfRange(4, totalCommand.size)
|
||||||
|
|
||||||
|
while (remainingCommand.size > 15) {
|
||||||
|
header[3] = pkgIndex.toByte()
|
||||||
|
tmp = header + remainingCommand.copyOfRange(0, 15)
|
||||||
|
packages.add(tmp + calcCrc8(tmp, tmp.size).toByte())
|
||||||
|
|
||||||
|
remainingCommand = remainingCommand.copyOfRange(15, remainingCommand.size)
|
||||||
|
pkgIndex = (pkgIndex + 1) % 256
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add last package
|
||||||
|
header[3] = pkgIndex.toByte()
|
||||||
|
tmp = header + remainingCommand
|
||||||
|
packages.add(tmp + calcCrc8(tmp, tmp.size).toByte())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNextPacket(): ByteArray? {
|
||||||
|
var ret: ByteArray? = null
|
||||||
|
if (index < packages.size) {
|
||||||
|
ret = packages[index]
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fun allPacketsConsumed(): Boolean {
|
||||||
|
return !(index < packages.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calcCrc8(value: ByteArray, size: Int): Int {
|
||||||
|
var crc8 = 0
|
||||||
|
for (i in 0 until size) {
|
||||||
|
crc8 = CRC_8_TABLE[(value[i].toInt() and 255) xor (crc8 and 255)].toInt() and 255
|
||||||
|
}
|
||||||
|
return crc8
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.enums
|
||||||
|
|
||||||
|
enum class AlarmSetting(val code: Byte) {
|
||||||
|
LIGHT_VIBRATE_AND_BEEP(0),
|
||||||
|
LIGHT_AND_VIBRATE(1),
|
||||||
|
LIGHT_AND_BEEP(2),
|
||||||
|
LIGHT_ONLY(3),
|
||||||
|
VIBRATE_AND_BEEP(4),
|
||||||
|
VIBRATE_ONLY(5),
|
||||||
|
BEEP_ONLY(6),
|
||||||
|
NONE(7)
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.enums
|
||||||
|
|
||||||
|
enum class AlarmState {
|
||||||
|
NONE,
|
||||||
|
PUMP_LOW_BATTERY, // Mapped from error flag 1
|
||||||
|
PUMP_LOW_RESERVOIR, // Mapped from error flag 2
|
||||||
|
PUMP_EXPIRES_SOON, // Mapped from error flag 3
|
||||||
|
LOWBG_SUSPENDED, // Mapped from pump status 64
|
||||||
|
LOWBG_SUSPENDED2, // Mapped from pump status 65
|
||||||
|
AUTO_SUSPENDED, // Mapped from pump status 66
|
||||||
|
HMAX_SUSPENDED, // Mapped from pump status 67
|
||||||
|
DMAX_SUSPENDED, // Mapped from pump status 68
|
||||||
|
SUSPENDED, // Mapped from pump status 69
|
||||||
|
PAUSED, // Mapped from pump status 70
|
||||||
|
OCCLUSION, // Mapped from pump status 96
|
||||||
|
EXPIRED, // Mapped from pump status 97
|
||||||
|
RESERVOIR_EMPTY, // Mapped from pump status 98
|
||||||
|
PATCH_FAULT, // Mapped from pump status 99
|
||||||
|
PATCH_FAULT2, // Mapped from pump status 100
|
||||||
|
BASE_FAULT, // Mapped from pump status 101
|
||||||
|
BATTERY_OUT, // Mapped from pump status 102
|
||||||
|
NO_CALIBRATION // Mapped from pump status 103
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.enums
|
||||||
|
|
||||||
|
enum class BasalType {
|
||||||
|
NONE,
|
||||||
|
STANDARD,
|
||||||
|
EXERCISE,
|
||||||
|
HOLIDAY,
|
||||||
|
PROGRAM_A,
|
||||||
|
PROGRAM_B,
|
||||||
|
ABSOLUTE_TEMP,
|
||||||
|
RELATIVE_TEMP,
|
||||||
|
PROGRAM_C,
|
||||||
|
PROGRAM_D,
|
||||||
|
SICK,
|
||||||
|
AUTO,
|
||||||
|
NEW,
|
||||||
|
SUSPEND_LOW_GLUCOSE,
|
||||||
|
SUSPEND_PREDICT_LOW_GLUCOSE,
|
||||||
|
SUSPEND_AUTO,
|
||||||
|
SUSPEND_MORE_THAN_MAX_PER_HOUR,
|
||||||
|
SUSPEND_MORE_THAN_MAX_PER_DAY,
|
||||||
|
SUSPEND_MANUAL,
|
||||||
|
SUSPEND_KEY_LOST,
|
||||||
|
STOP_OCCLUSION,
|
||||||
|
STOP_EXPIRED,
|
||||||
|
STOP_EMPTY,
|
||||||
|
STOP_PATCH_FAULT,
|
||||||
|
STOP_PATCH_FAULT2,
|
||||||
|
STOP_BASE_FAULT,
|
||||||
|
STOP_DISCARD,
|
||||||
|
STOP_BATTERY_EXHAUSTED,
|
||||||
|
STOP,
|
||||||
|
PAUSE_INTERRUPT,
|
||||||
|
PRIME,
|
||||||
|
AUTO_MODE_START,
|
||||||
|
AUTO_MODE_EXIT,
|
||||||
|
AUTO_MODE_TARGET_100,
|
||||||
|
AUTO_MODE_TARGET_110,
|
||||||
|
AUTO_MODE_TARGET_120,
|
||||||
|
AUTO_MODE_BREAKFAST,
|
||||||
|
AUTO_MODE_LUNCH,
|
||||||
|
AUTO_MODE_DINNER,
|
||||||
|
AUTO_MODE_SNACK,
|
||||||
|
AUTO_MODE_EXERCISE_START,
|
||||||
|
AUTO_MODE_EXERCISE_EXIT;
|
||||||
|
|
||||||
|
fun isTempBasal(): Boolean {
|
||||||
|
return this == ABSOLUTE_TEMP || this == RELATIVE_TEMP
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isSuspendedByPump(): Boolean {
|
||||||
|
return this in SUSPEND_LOW_GLUCOSE..STOP
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.enums
|
||||||
|
|
||||||
|
enum class BolusType {
|
||||||
|
NONE,
|
||||||
|
NORMAL,
|
||||||
|
EXTENDED,
|
||||||
|
COMBI;
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.enums
|
||||||
|
|
||||||
|
enum class CommandType(val code: Byte) {
|
||||||
|
SYNCHRONIZE(3),
|
||||||
|
SUBSCRIBE(4),
|
||||||
|
AUTH_REQ(5),
|
||||||
|
GET_DEVICE_TYPE(6),
|
||||||
|
SET_TIME(10),
|
||||||
|
GET_TIME(11),
|
||||||
|
SET_TIME_ZONE(12),
|
||||||
|
PRIME(16),
|
||||||
|
ACTIVATE(18),
|
||||||
|
SET_BOLUS(19),
|
||||||
|
CANCEL_BOLUS(20),
|
||||||
|
SET_BASAL_PROFILE(21),
|
||||||
|
SET_TEMP_BASAL(24),
|
||||||
|
CANCEL_TEMP_BASAL(25),
|
||||||
|
RESUME_PUMP(29),
|
||||||
|
POLL_PATCH(30),
|
||||||
|
STOP_PATCH(31),
|
||||||
|
READ_BOLUS_STATE(34),
|
||||||
|
SET_PATCH(35),
|
||||||
|
SET_BOLUS_MOTOR(36),
|
||||||
|
GET_RECORD(99),
|
||||||
|
CLEAR_ALARM(115)
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.enums
|
||||||
|
|
||||||
|
enum class MedtrumPumpState(val state: Byte) {
|
||||||
|
NONE(0),
|
||||||
|
IDLE(1),
|
||||||
|
FILLED(2),
|
||||||
|
PRIMING(3),
|
||||||
|
PRIMED(4),
|
||||||
|
EJECTING(5),
|
||||||
|
EJECTED(6),
|
||||||
|
ACTIVE(32),
|
||||||
|
ACTIVE_ALT(33),
|
||||||
|
LOWBG_SUSPENDED(64),
|
||||||
|
LOWBG_SUSPENDED2(65),
|
||||||
|
AUTO_SUSPENDED(66),
|
||||||
|
HMAX_SUSPENDED(67),
|
||||||
|
DMAX_SUSPENDED(68),
|
||||||
|
SUSPENDED(69),
|
||||||
|
PAUSED(70),
|
||||||
|
OCCLUSION(96),
|
||||||
|
EXPIRED(97),
|
||||||
|
RESERVOIR_EMPTY(98),
|
||||||
|
PATCH_FAULT(99),
|
||||||
|
PATCH_FAULT2(100),
|
||||||
|
BASE_FAULT(101),
|
||||||
|
BATTERY_OUT(102),
|
||||||
|
NO_CALIBRATION(103),
|
||||||
|
STOPPED(128.toByte());
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromByte(state: Byte) = values().find { it.state == state }
|
||||||
|
?: throw IllegalAccessException("")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.interfaces.pump.DetailedBolusInfo
|
||||||
|
import info.nightscout.interfaces.pump.PumpSync
|
||||||
|
import info.nightscout.pump.medtrum.MedtrumPump
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.ACTIVATE
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.BasalType
|
||||||
|
import info.nightscout.pump.medtrum.extension.toByteArray
|
||||||
|
import info.nightscout.pump.medtrum.extension.toByte
|
||||||
|
import info.nightscout.interfaces.stats.TddCalculator
|
||||||
|
import info.nightscout.pump.medtrum.extension.toInt
|
||||||
|
import info.nightscout.pump.medtrum.extension.toLong
|
||||||
|
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.math.round
|
||||||
|
|
||||||
|
class ActivatePacket(injector: HasAndroidInjector, private val basalProfile: ByteArray) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
@Inject lateinit var medtrumPump: MedtrumPump
|
||||||
|
@Inject lateinit var tddCalculator: TddCalculator
|
||||||
|
@Inject lateinit var pumpSync: PumpSync
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val RESP_PATCH_ID_START = 6
|
||||||
|
private const val RESP_PATCH_ID_END = RESP_PATCH_ID_START + 4
|
||||||
|
private const val RESP_TIME_START = 10
|
||||||
|
private const val RESP_TIME_END = RESP_TIME_START + 4
|
||||||
|
private const val RESP_BASAL_TYPE_START = 14
|
||||||
|
private const val RESP_BASAL_TYPE_END = RESP_BASAL_TYPE_START + 1
|
||||||
|
private const val RESP_BASAL_VALUE_START = 15
|
||||||
|
private const val RESP_BASAL_VALUE_END = RESP_BASAL_VALUE_START + 2
|
||||||
|
private const val RESP_BASAL_SEQUENCE_START = 17
|
||||||
|
private const val RESP_BASAL_SEQUENCE_END = RESP_BASAL_SEQUENCE_START + 2
|
||||||
|
private const val RESP_BASAL_PATCH_ID_START = 19
|
||||||
|
private const val RESP_BASAL_PATCH_ID_END = RESP_BASAL_PATCH_ID_START + 2
|
||||||
|
private const val RESP_BASAL_START_TIME_START = 21
|
||||||
|
private const val RESP_BASAL_START_TIME_END = RESP_BASAL_START_TIME_START + 4
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = ACTIVATE.code
|
||||||
|
expectedMinRespLength = RESP_BASAL_START_TIME_END
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRequest(): ByteArray {
|
||||||
|
/**
|
||||||
|
* byte 0: opCode
|
||||||
|
* byte 1: autoSuspendEnable // Value for auto mode, not used for AAPS
|
||||||
|
* byte 2: autoSuspendTime // Value for auto mode, not used for AAPS
|
||||||
|
* byte 3: expirationTimer // Expiration timer, 0 = no expiration 1 = 12 hour reminder and expiration after 3 days
|
||||||
|
* byte 4: alarmSetting // See AlarmSetting
|
||||||
|
* byte 5: lowSuspend // Value for auto mode, not used for AAPS
|
||||||
|
* byte 6: predictiveLowSuspend // Value for auto mode, not used for AAPS
|
||||||
|
* byte 7: predictiveLowSuspendRange // Value for auto mode, not used for AAPS
|
||||||
|
* byte 8-9: hourlyMaxInsulin // Max hourly dose of insulin, divided by 0.05
|
||||||
|
* byte 10-11: daylyMaxSet // Max daily dose of insulin, divided by 0.05
|
||||||
|
* byte 12-13: tddToday // Current TDD (of present day), divided by 0.05
|
||||||
|
* byte 14: 1 // Always 1
|
||||||
|
* bytes 15 - end // Basal profile > see MedtrumPump
|
||||||
|
*/
|
||||||
|
|
||||||
|
val autoSuspendEnable: Byte = 0
|
||||||
|
val autoSuspendTime: Byte = 12 // Not sure why, but pump needs this in order to activate
|
||||||
|
|
||||||
|
val patchExpiration: Byte = medtrumPump.desiredPatchExpiration.toByte()
|
||||||
|
val alarmSetting: Byte = medtrumPump.desiredAlarmSetting.code
|
||||||
|
|
||||||
|
val lowSuspend: Byte = 0
|
||||||
|
val predictiveLowSuspend: Byte = 0
|
||||||
|
val predictiveLowSuspendRange: Byte = 30 // Not sure why, but pump needs this in order to activate
|
||||||
|
|
||||||
|
val hourlyMaxInsulin: Int = round(medtrumPump.desiredHourlyMaxInsulin / 0.05).toInt()
|
||||||
|
val dailyMaxInsulin: Int = round(medtrumPump.desiredDailyMaxInsulin / 0.05).toInt()
|
||||||
|
val currentTDD: Double = tddCalculator.calculateToday()?.totalAmount?.div(0.05) ?: 0.0
|
||||||
|
|
||||||
|
return byteArrayOf(opCode) + autoSuspendEnable + autoSuspendTime + patchExpiration + alarmSetting + lowSuspend + predictiveLowSuspend + predictiveLowSuspendRange + hourlyMaxInsulin.toByteArray(
|
||||||
|
2
|
||||||
|
) + dailyMaxInsulin.toByteArray(2) + currentTDD.toInt().toByteArray(2) + 1.toByte() + basalProfile
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleResponse(data: ByteArray): Boolean {
|
||||||
|
val success = super.handleResponse(data)
|
||||||
|
if (success) {
|
||||||
|
val medtrumTimeUtil = MedtrumTimeUtil()
|
||||||
|
|
||||||
|
val patchId = data.copyOfRange(RESP_PATCH_ID_START, RESP_PATCH_ID_END).toLong()
|
||||||
|
val time = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_TIME_START, RESP_TIME_END).toLong())
|
||||||
|
val basalType = enumValues<BasalType>()[data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()]
|
||||||
|
val basalValue = data.copyOfRange(RESP_BASAL_VALUE_START, RESP_BASAL_VALUE_END).toInt() * 0.05
|
||||||
|
val basalSequence = data.copyOfRange(RESP_BASAL_SEQUENCE_START, RESP_BASAL_SEQUENCE_END).toInt()
|
||||||
|
val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toLong()
|
||||||
|
val basalStartTime = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong())
|
||||||
|
|
||||||
|
medtrumPump.patchId = patchId
|
||||||
|
medtrumPump.lastTimeReceivedFromPump = time
|
||||||
|
medtrumPump.currentSequenceNumber = basalSequence // We are activated, set the new seq nr
|
||||||
|
medtrumPump.syncedSequenceNumber = basalSequence // We are activated, reset the synced seq nr ()
|
||||||
|
|
||||||
|
// Sync canula change
|
||||||
|
pumpSync.insertTherapyEventIfNewWithTimestamp(
|
||||||
|
timestamp = System.currentTimeMillis(),
|
||||||
|
type = DetailedBolusInfo.EventType.CANNULA_CHANGE,
|
||||||
|
pumpType = medtrumPump.pumpType(),
|
||||||
|
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
|
||||||
|
)
|
||||||
|
pumpSync.insertTherapyEventIfNewWithTimestamp(
|
||||||
|
timestamp = System.currentTimeMillis(),
|
||||||
|
type = DetailedBolusInfo.EventType.INSULIN_CHANGE,
|
||||||
|
pumpType = medtrumPump.pumpType(),
|
||||||
|
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update the actual basal profile
|
||||||
|
medtrumPump.actualBasalProfile = basalProfile
|
||||||
|
medtrumPump.handleBasalStatusUpdate(basalType, basalValue, basalSequence, basalPatchId, basalStartTime, time)
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.MedtrumPump
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.AUTH_REQ
|
||||||
|
import info.nightscout.pump.medtrum.encryption.Crypt
|
||||||
|
import info.nightscout.pump.medtrum.extension.toByteArray
|
||||||
|
import info.nightscout.pump.medtrum.extension.toInt
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AuthorizePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
@Inject lateinit var medtrumPump: MedtrumPump
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val RESP_DEVICE_TYPE_START = 7
|
||||||
|
private const val RESP_DEVICE_TYPE_END = RESP_DEVICE_TYPE_START + 1
|
||||||
|
private const val RESP_VERSION_X_START = 8
|
||||||
|
private const val RESP_VERSION_X_END = RESP_VERSION_X_START + 1
|
||||||
|
private const val RESP_VERSION_Y_START = 9
|
||||||
|
private const val RESP_VERSION_Y_END = RESP_VERSION_Y_START + 1
|
||||||
|
private const val RESP_VERSION_Z_START = 10
|
||||||
|
private const val RESP_VERSION_Z_END = RESP_VERSION_Z_START + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = AUTH_REQ.code
|
||||||
|
expectedMinRespLength = RESP_VERSION_Z_END
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRequest(): ByteArray {
|
||||||
|
val role = 2 // Fixed to 2 for pump
|
||||||
|
val key = Crypt().keyGen(medtrumPump.pumpSN)
|
||||||
|
return byteArrayOf(opCode) + byteArrayOf(role.toByte()) + medtrumPump.patchSessionToken.toByteArray(4) + key.toByteArray(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleResponse(data: ByteArray): Boolean {
|
||||||
|
val success = super.handleResponse(data)
|
||||||
|
if (success) {
|
||||||
|
val deviceType = data.copyOfRange(RESP_DEVICE_TYPE_START, RESP_DEVICE_TYPE_END).toInt()
|
||||||
|
val swVersion = "" + data.copyOfRange(RESP_VERSION_X_START, RESP_VERSION_X_END).toInt() + "." + data.copyOfRange(RESP_VERSION_Y_START, RESP_VERSION_Y_END).toInt() + "." + data.copyOfRange(
|
||||||
|
RESP_VERSION_Z_START, RESP_VERSION_Z_END
|
||||||
|
).toInt()
|
||||||
|
|
||||||
|
if (medtrumPump.deviceType != deviceType) {
|
||||||
|
medtrumPump.deviceType = deviceType
|
||||||
|
}
|
||||||
|
if (medtrumPump.swVersion != swVersion) {
|
||||||
|
medtrumPump.swVersion = swVersion
|
||||||
|
}
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "GetDeviceTypeState: deviceType: ${deviceType}, swVersion: ${swVersion}")
|
||||||
|
}
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.CANCEL_BOLUS
|
||||||
|
|
||||||
|
class CancelBolusPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = CANCEL_BOLUS.code
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRequest(): ByteArray {
|
||||||
|
// Bolus types:
|
||||||
|
// 1 = normal
|
||||||
|
// 2 = Extended
|
||||||
|
// 3 = Combi
|
||||||
|
val bolusType: Byte = 1 // Only support for normal bolus for now
|
||||||
|
return byteArrayOf(opCode) + bolusType
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.MedtrumPump
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.CANCEL_TEMP_BASAL
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.BasalType
|
||||||
|
import info.nightscout.pump.medtrum.extension.toInt
|
||||||
|
import info.nightscout.pump.medtrum.extension.toLong
|
||||||
|
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import info.nightscout.shared.utils.DateUtil
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CancelTempBasalPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
@Inject lateinit var medtrumPump: MedtrumPump
|
||||||
|
@Inject lateinit var dateUtil: DateUtil
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val RESP_BASAL_TYPE_START = 6
|
||||||
|
private const val RESP_BASAL_TYPE_END = RESP_BASAL_TYPE_START + 1
|
||||||
|
private const val RESP_BASAL_RATE_START = RESP_BASAL_TYPE_END
|
||||||
|
private const val RESP_BASAL_RATE_END = RESP_BASAL_RATE_START + 2
|
||||||
|
private const val RESP_BASAL_SEQUENCE_START = RESP_BASAL_RATE_END
|
||||||
|
private const val RESP_BASAL_SEQUENCE_END = RESP_BASAL_SEQUENCE_START + 2
|
||||||
|
private const val RESP_BASAL_PATCH_ID_START = RESP_BASAL_SEQUENCE_END
|
||||||
|
private const val RESP_BASAL_PATCH_ID_END = RESP_BASAL_PATCH_ID_START + 2
|
||||||
|
private const val RESP_BASAL_START_TIME_START = RESP_BASAL_PATCH_ID_END
|
||||||
|
private const val RESP_BASAL_START_TIME_END = RESP_BASAL_START_TIME_START + 4
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = CANCEL_TEMP_BASAL.code
|
||||||
|
expectedMinRespLength = RESP_BASAL_START_TIME_END
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleResponse(data: ByteArray): Boolean {
|
||||||
|
val success = super.handleResponse(data)
|
||||||
|
if (success) {
|
||||||
|
val basalType = enumValues<BasalType>()[data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()]
|
||||||
|
val basalRate = data.copyOfRange(RESP_BASAL_RATE_START, RESP_BASAL_RATE_END).toInt() * 0.05
|
||||||
|
val basalSequence = data.copyOfRange(RESP_BASAL_SEQUENCE_START, RESP_BASAL_SEQUENCE_END).toInt()
|
||||||
|
val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toLong()
|
||||||
|
val basalStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong())
|
||||||
|
|
||||||
|
medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalStartTime)
|
||||||
|
}
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.CLEAR_ALARM
|
||||||
|
import info.nightscout.pump.medtrum.extension.toByteArray
|
||||||
|
|
||||||
|
class ClearPumpAlarmPacket(injector: HasAndroidInjector, val clearType: Int) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = CLEAR_ALARM.code
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRequest(): ByteArray {
|
||||||
|
return byteArrayOf(opCode) + clearType.toByteArray(2)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.GET_DEVICE_TYPE
|
||||||
|
import info.nightscout.pump.medtrum.extension.toInt
|
||||||
|
import info.nightscout.pump.medtrum.extension.toLong
|
||||||
|
|
||||||
|
class GetDeviceTypePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
var deviceType: Int = 0
|
||||||
|
var deviceSN: Long = 0
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val RESP_DEVICE_TYPE_START = 6
|
||||||
|
private const val RESP_DEVICE_TYPE_END = RESP_DEVICE_TYPE_START + 1
|
||||||
|
private const val RESP_DEVICE_SN_START = 7
|
||||||
|
private const val RESP_DEVICE_SN_END = RESP_DEVICE_SN_START + 4
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = GET_DEVICE_TYPE.code
|
||||||
|
expectedMinRespLength = RESP_DEVICE_SN_END
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleResponse(data: ByteArray): Boolean {
|
||||||
|
val success = super.handleResponse(data)
|
||||||
|
if (success) {
|
||||||
|
deviceType = data.copyOfRange(RESP_DEVICE_TYPE_START, RESP_DEVICE_TYPE_END).toInt()
|
||||||
|
deviceSN = data.copyOfRange(RESP_DEVICE_SN_START, RESP_DEVICE_SN_END).toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,353 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.interfaces.pump.DetailedBolusInfoStorage
|
||||||
|
import info.nightscout.interfaces.pump.PumpSync
|
||||||
|
import info.nightscout.interfaces.pump.TemporaryBasalStorage
|
||||||
|
import info.nightscout.pump.medtrum.MedtrumPump
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.GET_RECORD
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.BasalType
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.BolusType
|
||||||
|
import info.nightscout.pump.medtrum.extension.toByteArray
|
||||||
|
import info.nightscout.pump.medtrum.extension.toFloat
|
||||||
|
import info.nightscout.pump.medtrum.extension.toInt
|
||||||
|
import info.nightscout.pump.medtrum.extension.toLong
|
||||||
|
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import info.nightscout.shared.utils.DateUtil
|
||||||
|
import info.nightscout.shared.utils.T
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetRecordPacket(injector: HasAndroidInjector, private val recordIndex: Int) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
@Inject lateinit var medtrumPump: MedtrumPump
|
||||||
|
@Inject lateinit var pumpSync: PumpSync
|
||||||
|
@Inject lateinit var temporaryBasalStorage: TemporaryBasalStorage
|
||||||
|
@Inject lateinit var detailedBolusInfoStorage: DetailedBolusInfoStorage
|
||||||
|
@Inject lateinit var dateUtil: DateUtil
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val RESP_RECORD_HEADER_START = 6
|
||||||
|
private const val RESP_RECORD_HEADER_END = RESP_RECORD_HEADER_START + 1
|
||||||
|
private const val RESP_RECORD_UNKNOWN_START = RESP_RECORD_HEADER_END
|
||||||
|
private const val RESP_RECORD_UNKNOWN_END = RESP_RECORD_UNKNOWN_START + 1
|
||||||
|
private const val RESP_RECORD_TYPE_START = RESP_RECORD_UNKNOWN_END
|
||||||
|
private const val RESP_RECORD_TYPE_END = RESP_RECORD_TYPE_START + 1
|
||||||
|
private const val RESP_RECORD_UNKNOWN1_START = RESP_RECORD_TYPE_END
|
||||||
|
private const val RESP_RECORD_UNKNOWN1_END = RESP_RECORD_UNKNOWN1_START + 1
|
||||||
|
private const val RESP_RECORD_SERIAL_START = RESP_RECORD_UNKNOWN1_END
|
||||||
|
private const val RESP_RECORD_SERIAL_END = RESP_RECORD_SERIAL_START + 4
|
||||||
|
private const val RESP_RECORD_PATCHID_START = RESP_RECORD_SERIAL_END
|
||||||
|
private const val RESP_RECORD_PATCHID_END = RESP_RECORD_PATCHID_START + 2
|
||||||
|
private const val RESP_RECORD_SEQUENCE_START = RESP_RECORD_PATCHID_END
|
||||||
|
private const val RESP_RECORD_SEQUENCE_END = RESP_RECORD_SEQUENCE_START + 2
|
||||||
|
private const val RESP_RECORD_DATA_START = RESP_RECORD_SEQUENCE_END
|
||||||
|
|
||||||
|
private const val VALID_HEADER = 170
|
||||||
|
private const val BOLUS_RECORD = 1
|
||||||
|
private const val BOLUS_RECORD_ALT = 65
|
||||||
|
private const val BASAL_RECORD = 2
|
||||||
|
private const val BASAL_RECORD_ALT = 66
|
||||||
|
private const val ALARM_RECORD = 3
|
||||||
|
private const val AUTO_RECORD = 4
|
||||||
|
private const val TIME_SYNC_RECORD = 5
|
||||||
|
private const val AUTO1_RECORD = 6
|
||||||
|
private const val AUTO2_RECORD = 7
|
||||||
|
private const val AUTO3_RECORD = 8
|
||||||
|
private const val TDD_RECORD = 9
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = GET_RECORD.code
|
||||||
|
expectedMinRespLength = RESP_RECORD_DATA_START
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRequest(): ByteArray {
|
||||||
|
return byteArrayOf(opCode) + recordIndex.toByteArray(2) + medtrumPump.patchId.toByteArray(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleResponse(data: ByteArray): Boolean {
|
||||||
|
val success = super.handleResponse(data)
|
||||||
|
if (success) {
|
||||||
|
val recordHeader = data.copyOfRange(RESP_RECORD_HEADER_START, RESP_RECORD_HEADER_END).toInt()
|
||||||
|
val recordUnknown = data.copyOfRange(RESP_RECORD_UNKNOWN_START, RESP_RECORD_UNKNOWN_END).toInt()
|
||||||
|
val recordType = data.copyOfRange(RESP_RECORD_TYPE_START, RESP_RECORD_TYPE_END).toInt()
|
||||||
|
val recordSerial = data.copyOfRange(RESP_RECORD_SERIAL_START, RESP_RECORD_SERIAL_END).toLong()
|
||||||
|
val recordPatchId = data.copyOfRange(RESP_RECORD_PATCHID_START, RESP_RECORD_PATCHID_END).toInt()
|
||||||
|
val recordSequence = data.copyOfRange(RESP_RECORD_SEQUENCE_START, RESP_RECORD_SEQUENCE_END).toInt()
|
||||||
|
|
||||||
|
aapsLogger.debug(
|
||||||
|
LTag.PUMPCOMM,
|
||||||
|
"GetRecordPacket HandleResponse: Record header: $recordHeader, unknown: $recordUnknown, type: $recordType, serial: $recordSerial, patchId: $recordPatchId, " + "sequence: $recordSequence"
|
||||||
|
)
|
||||||
|
|
||||||
|
medtrumPump.syncedSequenceNumber = recordSequence // Assume sync upwards
|
||||||
|
|
||||||
|
if (recordHeader == VALID_HEADER) {
|
||||||
|
when (recordType) {
|
||||||
|
BOLUS_RECORD, BOLUS_RECORD_ALT -> {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BOLUS_RECORD")
|
||||||
|
val typeAndWizard = data.copyOfRange(RESP_RECORD_DATA_START, RESP_RECORD_DATA_START + 1).toInt()
|
||||||
|
val bolusCause = data.copyOfRange(RESP_RECORD_DATA_START + 1, RESP_RECORD_DATA_START + 2).toInt()
|
||||||
|
val unknown = data.copyOfRange(RESP_RECORD_DATA_START + 2, RESP_RECORD_DATA_START + 4).toInt()
|
||||||
|
val bolusStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_RECORD_DATA_START + 4, RESP_RECORD_DATA_START + 8).toLong())
|
||||||
|
val bolusNormalAmount = data.copyOfRange(RESP_RECORD_DATA_START + 8, RESP_RECORD_DATA_START + 10).toInt() * 0.05
|
||||||
|
val bolusNormalDelivered = data.copyOfRange(RESP_RECORD_DATA_START + 10, RESP_RECORD_DATA_START + 12).toInt() * 0.05
|
||||||
|
val bolusExtendedAmount = data.copyOfRange(RESP_RECORD_DATA_START + 12, RESP_RECORD_DATA_START + 14).toInt() * 0.05
|
||||||
|
val bolusExtendedDuration = data.copyOfRange(RESP_RECORD_DATA_START + 14, RESP_RECORD_DATA_START + 16).toLong() * 1000
|
||||||
|
val bolusExtendedDelivered = data.copyOfRange(RESP_RECORD_DATA_START + 16, RESP_RECORD_DATA_START + 18).toInt() * 0.05
|
||||||
|
val bolusCarb = data.copyOfRange(RESP_RECORD_DATA_START + 18, RESP_RECORD_DATA_START + 20).toInt()
|
||||||
|
val bolusGlucose = data.copyOfRange(RESP_RECORD_DATA_START + 20, RESP_RECORD_DATA_START + 22).toInt()
|
||||||
|
val bolusIOB = data.copyOfRange(RESP_RECORD_DATA_START + 22, RESP_RECORD_DATA_START + 24).toInt()
|
||||||
|
val unkown1 = data.copyOfRange(RESP_RECORD_DATA_START + 24, RESP_RECORD_DATA_START + 26).toInt()
|
||||||
|
val unkown2 = data.copyOfRange(RESP_RECORD_DATA_START + 26, RESP_RECORD_DATA_START + 28).toInt()
|
||||||
|
val bolusType = enumValues<BolusType>()[typeAndWizard and 0x0F]
|
||||||
|
val bolusWizard = (typeAndWizard and 0xF0) != 0
|
||||||
|
aapsLogger.debug(
|
||||||
|
LTag.PUMPCOMM,
|
||||||
|
"GetRecordPacket HandleResponse: BOLUS_RECORD: typeAndWizard: $typeAndWizard, bolusCause: $bolusCause, unknown: $unknown, bolusStartTime: $bolusStartTime, " + "bolusNormalAmount: $bolusNormalAmount, bolusNormalDelivered: $bolusNormalDelivered, bolusExtendedAmount: $bolusExtendedAmount, bolusExtendedDuration: $bolusExtendedDuration, " + "bolusExtendedDelivered: $bolusExtendedDelivered, bolusCarb: $bolusCarb, bolusGlucose: $bolusGlucose, bolusIOB: $bolusIOB, unkown1: $unkown1, unkown2: $unkown2, " + "bolusType: $bolusType, bolusWizard: $bolusWizard"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (bolusType == BolusType.NORMAL) {
|
||||||
|
val detailedBolusInfo = detailedBolusInfoStorage.findDetailedBolusInfo(bolusStartTime, bolusNormalDelivered)
|
||||||
|
var newRecord = false
|
||||||
|
if (detailedBolusInfo != null) {
|
||||||
|
val syncOk = pumpSync.syncBolusWithTempId(
|
||||||
|
timestamp = bolusStartTime,
|
||||||
|
amount = bolusNormalDelivered,
|
||||||
|
temporaryId = detailedBolusInfo.timestamp,
|
||||||
|
type = detailedBolusInfo.bolusType,
|
||||||
|
pumpId = bolusStartTime,
|
||||||
|
pumpType = medtrumPump.pumpType(),
|
||||||
|
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
|
||||||
|
)
|
||||||
|
if (syncOk == false) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BOLUS_RECORD: Failed to sync bolus with tempId: ${detailedBolusInfo.timestamp}")
|
||||||
|
// detailedInfo can be from another similar record. Reinsert
|
||||||
|
detailedBolusInfoStorage.add(detailedBolusInfo)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newRecord = pumpSync.syncBolusWithPumpId(
|
||||||
|
timestamp = bolusStartTime,
|
||||||
|
amount = bolusNormalDelivered,
|
||||||
|
type = null,
|
||||||
|
pumpId = bolusStartTime,
|
||||||
|
pumpType = medtrumPump.pumpType(),
|
||||||
|
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
aapsLogger.debug(
|
||||||
|
LTag.PUMPCOMM,
|
||||||
|
"from record: ${if (newRecord) "**NEW** " else ""}EVENT BOLUS ${dateUtil.dateAndTimeString(bolusStartTime)} ($bolusStartTime) Bolus: ${bolusNormalDelivered}U "
|
||||||
|
)
|
||||||
|
if (bolusStartTime > medtrumPump.lastBolusTime) {
|
||||||
|
medtrumPump.lastBolusTime = bolusStartTime
|
||||||
|
medtrumPump.lastBolusAmount = bolusNormalDelivered
|
||||||
|
}
|
||||||
|
} else if (bolusType == BolusType.EXTENDED) {
|
||||||
|
val newRecord = pumpSync.syncExtendedBolusWithPumpId(
|
||||||
|
timestamp = bolusStartTime,
|
||||||
|
amount = bolusExtendedDelivered,
|
||||||
|
duration = bolusExtendedDuration,
|
||||||
|
isEmulatingTB = false,
|
||||||
|
pumpId = bolusStartTime,
|
||||||
|
pumpType = medtrumPump.pumpType(),
|
||||||
|
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
|
||||||
|
)
|
||||||
|
aapsLogger.debug(
|
||||||
|
LTag.PUMPCOMM,
|
||||||
|
"from record: ${if (newRecord) "**NEW** " else ""}EVENT EXTENDED BOLUS ${dateUtil.dateAndTimeString(bolusStartTime)} ($bolusStartTime) Bolus: ${bolusNormalDelivered}U "
|
||||||
|
)
|
||||||
|
} else if (bolusType == BolusType.COMBI) {
|
||||||
|
// Note, this should never happen, as we don't use combo bolus
|
||||||
|
val detailedBolusInfo = detailedBolusInfoStorage.findDetailedBolusInfo(bolusStartTime, bolusNormalDelivered)
|
||||||
|
val newRecord = pumpSync.syncBolusWithPumpId(
|
||||||
|
timestamp = bolusStartTime,
|
||||||
|
amount = bolusNormalDelivered,
|
||||||
|
type = detailedBolusInfo?.bolusType,
|
||||||
|
pumpId = bolusStartTime,
|
||||||
|
pumpType = medtrumPump.pumpType(),
|
||||||
|
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
|
||||||
|
)
|
||||||
|
pumpSync.syncExtendedBolusWithPumpId(
|
||||||
|
timestamp = bolusStartTime,
|
||||||
|
amount = bolusExtendedDelivered,
|
||||||
|
duration = bolusExtendedDuration,
|
||||||
|
isEmulatingTB = false,
|
||||||
|
pumpId = bolusStartTime,
|
||||||
|
pumpType = medtrumPump.pumpType(),
|
||||||
|
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
|
||||||
|
)
|
||||||
|
aapsLogger.error(
|
||||||
|
LTag.PUMPCOMM,
|
||||||
|
"from record: ${if (newRecord) "**NEW** " else ""}EVENT COMBI BOLUS ${dateUtil.dateAndTimeString(bolusStartTime)} ($bolusStartTime) Bolus: ${bolusNormalDelivered}U Extended: ${bolusExtendedDelivered} THIS SHOULD NOT HAPPEN!!!"
|
||||||
|
)
|
||||||
|
if (!newRecord && detailedBolusInfo != null) {
|
||||||
|
// detailedInfo can be from another similar record. Reinsert
|
||||||
|
detailedBolusInfoStorage.add(detailedBolusInfo)
|
||||||
|
}
|
||||||
|
if (bolusStartTime > medtrumPump.lastBolusTime) {
|
||||||
|
medtrumPump.lastBolusTime = bolusStartTime
|
||||||
|
medtrumPump.lastBolusAmount = bolusNormalDelivered
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BOLUS_RECORD: Unknown bolus type: $bolusType")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
BASAL_RECORD, BASAL_RECORD_ALT -> {
|
||||||
|
val medtrumTimeUtil = MedtrumTimeUtil()
|
||||||
|
val basalStartTime = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_RECORD_DATA_START, RESP_RECORD_DATA_START + 4).toLong())
|
||||||
|
val basalEndTime = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_RECORD_DATA_START + 4, RESP_RECORD_DATA_START + 8).toLong())
|
||||||
|
val basalType = enumValues<BasalType>()[data.copyOfRange(RESP_RECORD_DATA_START + 8, RESP_RECORD_DATA_START + 9).toInt()]
|
||||||
|
val basalEndReason = data.copyOfRange(RESP_RECORD_DATA_START + 9, RESP_RECORD_DATA_START + 10).toInt()
|
||||||
|
val basalRate = data.copyOfRange(RESP_RECORD_DATA_START + 10, RESP_RECORD_DATA_START + 12).toInt() * 0.05
|
||||||
|
val basalDelivered = data.copyOfRange(RESP_RECORD_DATA_START + 12, RESP_RECORD_DATA_START + 14).toInt() * 0.05
|
||||||
|
val basalPercent = data.copyOfRange(RESP_RECORD_DATA_START + 14, RESP_RECORD_DATA_START + 16).toInt()
|
||||||
|
|
||||||
|
aapsLogger.debug(
|
||||||
|
LTag.PUMPCOMM,
|
||||||
|
"GetRecordPacket HandleResponse: BASAL_RECORD: Start: $basalStartTime, End: $basalEndTime, Type: $basalType, EndReason: $basalEndReason, Rate: $basalRate, Delivered: $basalDelivered, Percent: $basalPercent"
|
||||||
|
)
|
||||||
|
|
||||||
|
when (basalType) {
|
||||||
|
BasalType.STANDARD -> {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BASAL_RECORD: Standard basal")
|
||||||
|
// If we are here it means the basal has ended
|
||||||
|
}
|
||||||
|
|
||||||
|
BasalType.ABSOLUTE_TEMP, BasalType.RELATIVE_TEMP -> {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BASAL_RECORD: Absolute temp basal")
|
||||||
|
var duration = (basalEndTime - basalStartTime)
|
||||||
|
// Work around for pumpSync not accepting 0 duration.
|
||||||
|
// sometimes we get 0 duration for very short basal because the pump only reports time in seconds
|
||||||
|
if (duration < 250) duration = 250 // 250ms to make sure AAPS accepts it
|
||||||
|
|
||||||
|
val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
|
||||||
|
timestamp = basalStartTime,
|
||||||
|
rate = if (basalType == BasalType.ABSOLUTE_TEMP) basalRate else basalPercent.toDouble(),
|
||||||
|
duration = duration,
|
||||||
|
isAbsolute = (basalType == BasalType.ABSOLUTE_TEMP),
|
||||||
|
type = PumpSync.TemporaryBasalType.NORMAL,
|
||||||
|
pumpId = basalStartTime,
|
||||||
|
pumpType = medtrumPump.pumpType(),
|
||||||
|
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
|
||||||
|
)
|
||||||
|
aapsLogger.debug(
|
||||||
|
LTag.PUMPCOMM,
|
||||||
|
"handleBasalStatusUpdate from record: ${if (newRecord) "**NEW** " else ""}EVENT TEMP_SYNC: ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) " +
|
||||||
|
"Rate: $basalRate Duration: ${duration}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
in BasalType.SUSPEND_LOW_GLUCOSE..BasalType.STOP -> {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BASAL_RECORD: Suspend basal")
|
||||||
|
val duration = (basalEndTime - basalStartTime)
|
||||||
|
val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
|
||||||
|
timestamp = basalEndTime,
|
||||||
|
rate = 0.0,
|
||||||
|
duration = duration,
|
||||||
|
isAbsolute = true,
|
||||||
|
type = PumpSync.TemporaryBasalType.PUMP_SUSPEND,
|
||||||
|
pumpId = basalStartTime,
|
||||||
|
pumpType = medtrumPump.pumpType(),
|
||||||
|
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
|
||||||
|
)
|
||||||
|
aapsLogger.debug(
|
||||||
|
LTag.PUMPCOMM,
|
||||||
|
"handleBasalStatusUpdate from record: ${if (newRecord) "**NEW** " else ""}EVENT SUSPEND: ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) " +
|
||||||
|
"Rate: $basalRate Duration: ${duration}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BASAL_RECORD: Unknown basal type: $basalType")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ALARM_RECORD -> {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: ALARM_RECORD")
|
||||||
|
}
|
||||||
|
|
||||||
|
AUTO_RECORD -> {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: AUTO_RECORD")
|
||||||
|
}
|
||||||
|
|
||||||
|
TIME_SYNC_RECORD -> {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: TIME_SYNC_RECORD")
|
||||||
|
}
|
||||||
|
|
||||||
|
AUTO1_RECORD -> {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: AUTO1_RECORD")
|
||||||
|
}
|
||||||
|
|
||||||
|
AUTO2_RECORD -> {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: AUTO2_RECORD")
|
||||||
|
}
|
||||||
|
|
||||||
|
AUTO3_RECORD -> {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: AUTO3_RECORD")
|
||||||
|
}
|
||||||
|
|
||||||
|
TDD_RECORD -> {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: TDD_RECORD")
|
||||||
|
val timestamp = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_RECORD_DATA_START, RESP_RECORD_DATA_START + 4).toLong())
|
||||||
|
val timeZoneOffset = data.copyOfRange(RESP_RECORD_DATA_START + 4, RESP_RECORD_DATA_START + 6).toInt()
|
||||||
|
val tddMins = data.copyOfRange(RESP_RECORD_DATA_START + 6, RESP_RECORD_DATA_START + 8).toInt()
|
||||||
|
val glucoseRecordTime = data.copyOfRange(RESP_RECORD_DATA_START + 8, RESP_RECORD_DATA_START + 12).toLong()
|
||||||
|
val tdd = data.copyOfRange(RESP_RECORD_DATA_START + 12, RESP_RECORD_DATA_START + 16).toFloat()
|
||||||
|
val basalTdd = data.copyOfRange(RESP_RECORD_DATA_START + 16, RESP_RECORD_DATA_START + 20).toFloat()
|
||||||
|
val glucose = data.copyOfRange(RESP_RECORD_DATA_START + 20, RESP_RECORD_DATA_START + 24).toFloat()
|
||||||
|
val unknown = data.copyOfRange(RESP_RECORD_DATA_START + 24, RESP_RECORD_DATA_START + 28).toFloat()
|
||||||
|
val meanSomething = data.copyOfRange(RESP_RECORD_DATA_START + 28, RESP_RECORD_DATA_START + 32).toFloat()
|
||||||
|
val usedTdd = data.copyOfRange(RESP_RECORD_DATA_START + 32, RESP_RECORD_DATA_START + 36).toFloat()
|
||||||
|
val usedIBasal = data.copyOfRange(RESP_RECORD_DATA_START + 36, RESP_RECORD_DATA_START + 40).toFloat()
|
||||||
|
val usedSgBasal = data.copyOfRange(RESP_RECORD_DATA_START + 40, RESP_RECORD_DATA_START + 44).toFloat()
|
||||||
|
val usedUMax = data.copyOfRange(RESP_RECORD_DATA_START + 44, RESP_RECORD_DATA_START + 48).toFloat()
|
||||||
|
val newTdd = data.copyOfRange(RESP_RECORD_DATA_START + 48, RESP_RECORD_DATA_START + 52).toFloat()
|
||||||
|
val newIBasal = data.copyOfRange(RESP_RECORD_DATA_START + 52, RESP_RECORD_DATA_START + 56).toFloat()
|
||||||
|
val newSgBasal = data.copyOfRange(RESP_RECORD_DATA_START + 56, RESP_RECORD_DATA_START + 60).toFloat()
|
||||||
|
val newUMax = data.copyOfRange(RESP_RECORD_DATA_START + 60, RESP_RECORD_DATA_START + 64).toFloat()
|
||||||
|
|
||||||
|
aapsLogger.debug(
|
||||||
|
LTag.PUMPCOMM, "TDD_RECORD: timestamp: $timestamp, timeZoneOffset: $timeZoneOffset, tddMins: $tddMins, glucoseRecordTime: $glucoseRecordTime, tdd: " +
|
||||||
|
"$tdd, basalTdd: $basalTdd, glucose: $glucose, unknown: $unknown, meanSomething: $meanSomething, usedTdd: $usedTdd, usedIBasal: $usedIBasal, usedSgBasal: " +
|
||||||
|
"$usedSgBasal, usedUMax: $usedUMax, newTdd: $newTdd, newIBasal: $newIBasal, newSgBasal: $newSgBasal, newUMax: $newUMax"
|
||||||
|
)
|
||||||
|
|
||||||
|
val newRecord = pumpSync.createOrUpdateTotalDailyDose(
|
||||||
|
timestamp = timestamp,
|
||||||
|
bolusAmount = (tdd - basalTdd).toDouble(),
|
||||||
|
basalAmount = basalTdd.toDouble(),
|
||||||
|
totalAmount = tdd.toDouble(),
|
||||||
|
pumpId = timestamp,
|
||||||
|
pumpType = medtrumPump.pumpType(),
|
||||||
|
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
|
||||||
|
)
|
||||||
|
|
||||||
|
aapsLogger.debug(
|
||||||
|
LTag.PUMPCOMM,
|
||||||
|
"handleBasalStatusUpdate from record: ${if (newRecord) "**NEW** " else ""}EVENT TDD: ${dateUtil.dateAndTimeString(timestamp)} ($timestamp) " +
|
||||||
|
"TDD: $tdd, BasalTDD: $basalTdd, BolusTDD: ${tdd - basalTdd}"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: Unknown record type: $recordType")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: Invalid record header")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.MedtrumPump
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.GET_TIME
|
||||||
|
import info.nightscout.pump.medtrum.extension.toLong
|
||||||
|
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetTimePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
@Inject lateinit var medtrumPump: MedtrumPump
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val RESP_TIME_START = 6
|
||||||
|
private const val RESP_TIME_END = RESP_TIME_START + 4
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = GET_TIME.code
|
||||||
|
expectedMinRespLength = RESP_TIME_END
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleResponse(data: ByteArray): Boolean {
|
||||||
|
val success = super.handleResponse(data)
|
||||||
|
if (success) {
|
||||||
|
val time = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_TIME_START, RESP_TIME_END).toLong())
|
||||||
|
medtrumPump.lastTimeReceivedFromPump = time
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.extension.toInt
|
||||||
|
import info.nightscout.rx.logging.AAPSLogger
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
open class MedtrumPacket(protected var injector: HasAndroidInjector) {
|
||||||
|
|
||||||
|
@Inject lateinit var aapsLogger: AAPSLogger
|
||||||
|
|
||||||
|
var opCode: Byte = 0
|
||||||
|
var failed = false
|
||||||
|
var expectedMinRespLength = RESP_RESULT_END
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val RESP_OPCODE_START = 1
|
||||||
|
const val RESP_OPCODE_END = RESP_OPCODE_START + 1
|
||||||
|
const val RESP_RESULT_START = 4
|
||||||
|
const val RESP_RESULT_END = RESP_RESULT_START + 2
|
||||||
|
|
||||||
|
private const val RESP_WAITING = 16384
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
// @Suppress("LeakingThis")
|
||||||
|
injector.androidInjector().inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun getRequest(): ByteArray {
|
||||||
|
return byteArrayOf(opCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** handles a response from the Medtrum pump, returns true if command was successfull, returns false if command failed or waiting for response */
|
||||||
|
open fun handleResponse(data: ByteArray): Boolean {
|
||||||
|
// Check for broken packets
|
||||||
|
if (RESP_RESULT_END > data.size) {
|
||||||
|
failed = true
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "handleResponse: Unexpected response length, expected: $expectedMinRespLength got: ${data.size}")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val incomingOpCode: Byte = data.copyOfRange(RESP_OPCODE_START, RESP_OPCODE_END).first()
|
||||||
|
val responseCode = data.copyOfRange(RESP_RESULT_START, RESP_RESULT_END).toInt()
|
||||||
|
|
||||||
|
return when {
|
||||||
|
incomingOpCode != opCode -> {
|
||||||
|
failed = true
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "handleResponse: Unexpected command, expected: $opCode got: $incomingOpCode")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
responseCode == 0 -> {
|
||||||
|
// Check if length is what is expected from this type of packet
|
||||||
|
if (expectedMinRespLength > data.size) {
|
||||||
|
failed = true
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "handleResponse: Unexpected response length, expected: $expectedMinRespLength got: ${data.size}")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "handleResponse: Happy command: $opCode response: $responseCode")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
responseCode == RESP_WAITING -> {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "handleResponse: Waiting command: $opCode response: $responseCode")
|
||||||
|
// Waiting do nothing
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
failed = true
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "handleResponse: Error in command: $opCode response: $responseCode")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,236 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.MedtrumPump
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.AlarmState
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.BasalType
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
|
||||||
|
import info.nightscout.pump.medtrum.extension.toInt
|
||||||
|
import info.nightscout.pump.medtrum.extension.toLong
|
||||||
|
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
|
||||||
|
import info.nightscout.rx.logging.AAPSLogger
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class NotificationPacket(val injector: HasAndroidInjector) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a bit of a special packet, as it is not a command packet
|
||||||
|
* but a notification packet. It is sent by the pump to the phone
|
||||||
|
* when the pump has a notification to send.
|
||||||
|
*
|
||||||
|
* Notifications are sent regualary, regardless of the pump state.
|
||||||
|
*
|
||||||
|
* There can be multiple messages in one packet, this is noted by the fieldMask.
|
||||||
|
*
|
||||||
|
* Byte 1: State (Handle a state change directly? before analyzing further?)
|
||||||
|
* Byte 2-3: FieldMask (BitMask which tells the fields present in the message)
|
||||||
|
* Byte 4-end : status data
|
||||||
|
*
|
||||||
|
* When multiple fields are in the message, the data is concatenated.
|
||||||
|
* This kind of message can also come as a response of SynchronizePacket,
|
||||||
|
* and can be handled here by handleMaskedMessage() as well.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Inject lateinit var aapsLogger: AAPSLogger
|
||||||
|
@Inject lateinit var medtrumPump: MedtrumPump
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val NOTIF_STATE_START = 0
|
||||||
|
private const val NOTIF_STATE_END = NOTIF_STATE_START + 1
|
||||||
|
|
||||||
|
private const val MASK_SUSPEND = 0x01
|
||||||
|
private const val MASK_NORMAL_BOLUS = 0x02
|
||||||
|
private const val MASK_EXTENDED_BOLUS = 0x04
|
||||||
|
private const val MASK_BASAL = 0x08
|
||||||
|
|
||||||
|
private const val MASK_SETUP = 0x10
|
||||||
|
private const val MASK_RESERVOIR = 0x20
|
||||||
|
private const val MASK_START_TIME = 0x40
|
||||||
|
private const val MASK_BATTERY = 0x80
|
||||||
|
|
||||||
|
private const val MASK_STORAGE = 0x100
|
||||||
|
private const val MASK_ALARM = 0x200
|
||||||
|
private const val MASK_AGE = 0x400
|
||||||
|
private const val MASK_UNKNOWN_1 = 0x800
|
||||||
|
|
||||||
|
private const val MASK_UNUSED_CGM = 0x1000
|
||||||
|
private const val MASK_UNUSED_COMMAND_CONFIRM = 0x2000
|
||||||
|
private const val MASK_UNUSED_AUTO_STATUS = 0x4000
|
||||||
|
private const val MASK_UNUSED_LEGACY = 0x8000
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
injector.androidInjector().inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleNotification(notification: ByteArray) {
|
||||||
|
val state = MedtrumPumpState.fromByte(notification[0])
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Notification state: $state, current state: ${medtrumPump.pumpState}")
|
||||||
|
|
||||||
|
if (state != medtrumPump.pumpState) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "State changed from ${medtrumPump.pumpState} to $state")
|
||||||
|
medtrumPump.pumpState = state
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notification.size > NOTIF_STATE_END) {
|
||||||
|
handleMaskedMessage(notification.copyOfRange(NOTIF_STATE_END, notification.size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a message with a field mask, can be used by other packets as well
|
||||||
|
*/
|
||||||
|
fun handleMaskedMessage(data: ByteArray) {
|
||||||
|
val fieldMask = data.copyOfRange(0, 2).toInt()
|
||||||
|
var offset = 2
|
||||||
|
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Message field mask: $fieldMask")
|
||||||
|
|
||||||
|
if (fieldMask and MASK_SUSPEND != 0) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Suspend notification received")
|
||||||
|
medtrumPump.suspendTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong())
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Suspend time: ${medtrumPump.suspendTime}")
|
||||||
|
offset += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMask and MASK_NORMAL_BOLUS != 0) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Normal bolus notification received")
|
||||||
|
var bolusData = data.copyOfRange(offset, offset + 1).toInt()
|
||||||
|
var bolusType = bolusData and 0x7F
|
||||||
|
val bolusCompleted: Boolean = ((bolusData shr 7) and 0x01) != 0
|
||||||
|
var bolusDelivered = data.copyOfRange(offset + 1, offset + 3).toInt() * 0.05
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Bolus type: $bolusType, bolusData: $bolusData bolus completed: $bolusCompleted, bolus delivered: $bolusDelivered")
|
||||||
|
medtrumPump.handleBolusStatusUpdate(bolusType, bolusCompleted, bolusDelivered)
|
||||||
|
offset += 3
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMask and MASK_EXTENDED_BOLUS != 0) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "Extended bolus notification received, extended bolus not supported!")
|
||||||
|
// TODO Handle error and stop pump if this happens?
|
||||||
|
offset += 3
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMask and MASK_BASAL != 0) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Basal notification received")
|
||||||
|
val basalType = enumValues<BasalType>()[data.copyOfRange(offset, offset + 1).toInt()]
|
||||||
|
var basalSequence = data.copyOfRange(offset + 1, offset + 3).toInt()
|
||||||
|
var basalPatchId = data.copyOfRange(offset + 3, offset + 5).toLong()
|
||||||
|
var basalStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset + 5, offset + 9).toLong())
|
||||||
|
var basalRateAndDelivery = data.copyOfRange(offset + 9, offset + 12).toInt()
|
||||||
|
var basalRate = (basalRateAndDelivery and 0xFFF) * 0.05
|
||||||
|
var basalDelivery = (basalRateAndDelivery shr 12) * 0.05
|
||||||
|
aapsLogger.debug(
|
||||||
|
LTag.PUMPCOMM,
|
||||||
|
"Basal type: $basalType, basal sequence: $basalSequence, basal patch id: $basalPatchId, basal time: $basalStartTime, basal rate: $basalRate, basal delivery: $basalDelivery"
|
||||||
|
)
|
||||||
|
// Don't spam with basal updates here, only if the running basal rate has changed, or a new basal is set
|
||||||
|
if (medtrumPump.lastBasalRate != basalRate || medtrumPump.lastBasalStartTime != basalStartTime) {
|
||||||
|
medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalStartTime)
|
||||||
|
}
|
||||||
|
offset += 12
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMask and MASK_SETUP != 0) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Setup notification received")
|
||||||
|
medtrumPump.primeProgress = data.copyOfRange(offset, offset + 1).toInt()
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Prime progress: ${medtrumPump.primeProgress}")
|
||||||
|
offset += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMask and MASK_RESERVOIR != 0) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Reservoir notification received")
|
||||||
|
medtrumPump.reservoir = data.copyOfRange(offset, offset + 2).toInt() * 0.05
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Reservoir: ${medtrumPump.reservoir}")
|
||||||
|
offset += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMask and MASK_START_TIME != 0) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Start time notification received")
|
||||||
|
val patchStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong())
|
||||||
|
if (medtrumPump.patchStartTime != patchStartTime) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Patch start time changed from ${medtrumPump.patchStartTime} to $patchStartTime")
|
||||||
|
medtrumPump.patchStartTime = patchStartTime
|
||||||
|
}
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Patch start time: ${patchStartTime}")
|
||||||
|
offset += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMask and MASK_BATTERY != 0) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Battery notification received")
|
||||||
|
var parameter = data.copyOfRange(offset, offset + 3).toInt()
|
||||||
|
// Precision for voltage A is a guess, voltage B is the important one, threshold: < 2.64
|
||||||
|
medtrumPump.batteryVoltage_A = (parameter and 0xFFF) / 512.0
|
||||||
|
medtrumPump.batteryVoltage_B = (parameter shr 12) / 512.0
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Battery voltage A: ${medtrumPump.batteryVoltage_A}, battery voltage B: ${medtrumPump.batteryVoltage_B}")
|
||||||
|
offset += 3
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMask and MASK_STORAGE != 0) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Storage notification received")
|
||||||
|
val sequence = data.copyOfRange(offset, offset + 2).toInt()
|
||||||
|
if (sequence > medtrumPump.currentSequenceNumber) {
|
||||||
|
medtrumPump.currentSequenceNumber = sequence
|
||||||
|
}
|
||||||
|
val patchId = data.copyOfRange(offset + 2, offset + 4).toLong()
|
||||||
|
if (patchId != medtrumPump.patchId) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "handleMaskedMessage: We got wrong patch id!")
|
||||||
|
}
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Last known sequence number: ${medtrumPump.currentSequenceNumber}, patch id: ${patchId}")
|
||||||
|
offset += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMask and MASK_ALARM != 0) {
|
||||||
|
val alarmFlags = data.copyOfRange(offset, offset + 2).toInt()
|
||||||
|
val alarmParameter = data.copyOfRange(offset + 2, offset + 4).toInt()
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Alarm notification received, Alarm flags: $alarmFlags, alarm parameter: $alarmParameter")
|
||||||
|
|
||||||
|
// If no alarm, clear activeAlarm list
|
||||||
|
if (alarmFlags == 0 && medtrumPump.activeAlarms.isNotEmpty()) {
|
||||||
|
medtrumPump.clearAlarmState()
|
||||||
|
} else if (alarmFlags != 0) {
|
||||||
|
// Check each alarm bit
|
||||||
|
for (i in 0..3) { // Only the first 3 flags are interesting for us, the rest we will get from the pump state
|
||||||
|
val alarmState = AlarmState.values()[i]
|
||||||
|
if ((alarmFlags shr i) and 1 != 0) {
|
||||||
|
// If the alarm bit is set, add the corresponding alarm to activeAlarms
|
||||||
|
medtrumPump.addAlarm(alarmState)
|
||||||
|
} else if (medtrumPump.activeAlarms.contains(alarmState)) {
|
||||||
|
// If the alarm bit is not set, and the corresponding alarm is in activeAlarms, remove it
|
||||||
|
medtrumPump.removeAlarm(alarmState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMask and MASK_AGE != 0) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Age notification received")
|
||||||
|
medtrumPump.patchAge = data.copyOfRange(offset, offset + 4).toLong()
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Patch age: ${medtrumPump.patchAge}")
|
||||||
|
offset += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMask and MASK_UNKNOWN_1 != 0) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Unknown 1 notification received, not handled!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMask and MASK_UNUSED_CGM != 0) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Unused CGM notification received, not handled!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMask and MASK_UNUSED_COMMAND_CONFIRM != 0) {
|
||||||
|
// This one is a warning, as this happens we need to know about it, and maybe implement
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "Unused command confirm notification received, not handled!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMask and MASK_UNUSED_AUTO_STATUS != 0) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Unused auto status notification received, not handled!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMask and MASK_UNUSED_LEGACY != 0) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Unused legacy notification received, not handled!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.POLL_PATCH
|
||||||
|
|
||||||
|
class PollPatchPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = POLL_PATCH.code
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.PRIME
|
||||||
|
|
||||||
|
class PrimePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = PRIME.code
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.READ_BOLUS_STATE
|
||||||
|
|
||||||
|
class ReadBolusStatePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
|
||||||
|
// UNUSED
|
||||||
|
// Bolus sync is currently done by getting the records and syncing then with AAPS pumpSync there
|
||||||
|
|
||||||
|
var bolusData: ByteArray = byteArrayOf()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val RESP_BOLUS_DATA_START = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = READ_BOLUS_STATE.code
|
||||||
|
expectedMinRespLength = RESP_BOLUS_DATA_START + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleResponse(data: ByteArray): Boolean {
|
||||||
|
val success = super.handleResponse(data)
|
||||||
|
if (success) {
|
||||||
|
// UNUSED
|
||||||
|
bolusData = data.copyOfRange(RESP_BOLUS_DATA_START, data.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.RESUME_PUMP
|
||||||
|
import info.nightscout.pump.medtrum.extension.toByteArray
|
||||||
|
|
||||||
|
class ResumePumpPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = RESUME_PUMP.code
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRequest(): ByteArray {
|
||||||
|
return byteArrayOf(opCode)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.MedtrumPump
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_BASAL_PROFILE
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.BasalType
|
||||||
|
import info.nightscout.pump.medtrum.extension.toInt
|
||||||
|
import info.nightscout.pump.medtrum.extension.toLong
|
||||||
|
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SetBasalProfilePacket(injector: HasAndroidInjector, private val basalProfile: ByteArray) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
@Inject lateinit var medtrumPump: MedtrumPump
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val RESP_BASAL_TYPE_START = 6
|
||||||
|
private const val RESP_BASAL_TYPE_END = RESP_BASAL_TYPE_START + 1
|
||||||
|
private const val RESP_BASAL_VALUE_START = 7
|
||||||
|
private const val RESP_BASAL_VALUE_END = RESP_BASAL_VALUE_START + 2
|
||||||
|
private const val RESP_BASAL_SEQUENCE_START = 9
|
||||||
|
private const val RESP_BASAL_SEQUENCE_END = RESP_BASAL_SEQUENCE_START + 2
|
||||||
|
private const val RESP_BASAL_PATCH_ID_START = 11
|
||||||
|
private const val RESP_BASAL_PATCH_ID_END = RESP_BASAL_PATCH_ID_START + 2
|
||||||
|
private const val RESP_BASAL_START_TIME_START = 13
|
||||||
|
private const val RESP_BASAL_START_TIME_END = RESP_BASAL_START_TIME_START + 4
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = SET_BASAL_PROFILE.code
|
||||||
|
expectedMinRespLength = RESP_BASAL_START_TIME_END
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRequest(): ByteArray {
|
||||||
|
val basalType: Byte = 1 // Fixed to normal basal
|
||||||
|
return byteArrayOf(opCode) + basalType + basalProfile
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleResponse(data: ByteArray): Boolean {
|
||||||
|
val success = super.handleResponse(data)
|
||||||
|
if (success) {
|
||||||
|
val medtrumTimeUtil = MedtrumTimeUtil()
|
||||||
|
val basalType = enumValues<BasalType>()[data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()]
|
||||||
|
val basalValue = data.copyOfRange(RESP_BASAL_VALUE_START, RESP_BASAL_VALUE_END).toInt() * 0.05
|
||||||
|
val basalSequence = data.copyOfRange(RESP_BASAL_SEQUENCE_START, RESP_BASAL_SEQUENCE_END).toInt()
|
||||||
|
val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toLong()
|
||||||
|
val basalStartTime = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong())
|
||||||
|
|
||||||
|
// Update the actual basal profile
|
||||||
|
medtrumPump.actualBasalProfile = basalProfile
|
||||||
|
medtrumPump.handleBasalStatusUpdate(basalType, basalValue, basalSequence, basalPatchId, basalStartTime)
|
||||||
|
}
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_BOLUS_MOTOR
|
||||||
|
|
||||||
|
class SetBolusMotorPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
// UNUSED in our driver
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = SET_BOLUS_MOTOR.code
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRequest(): ByteArray {
|
||||||
|
return byteArrayOf(opCode) + 0.toByte()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_BOLUS
|
||||||
|
import info.nightscout.pump.medtrum.extension.toByteArray
|
||||||
|
import kotlin.math.round
|
||||||
|
|
||||||
|
class SetBolusPacket(injector: HasAndroidInjector, private val insulin: Double) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = SET_BOLUS.code
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRequest(): ByteArray {
|
||||||
|
// Bolus types:
|
||||||
|
// 1 = normal
|
||||||
|
// 2 = Extended
|
||||||
|
// 3 = Combi
|
||||||
|
val bolusType: Byte = 1 // Only support for normal bolus for now
|
||||||
|
val bolusAmount: Int = round(insulin / 0.05).toInt()
|
||||||
|
return byteArrayOf(opCode) + bolusType + bolusAmount.toByteArray(2) + 0.toByte()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.MedtrumPump
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.AlarmSetting
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_PATCH
|
||||||
|
import info.nightscout.pump.medtrum.extension.toByte
|
||||||
|
import info.nightscout.pump.medtrum.extension.toByteArray
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.math.round
|
||||||
|
|
||||||
|
class SetPatchPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
@Inject lateinit var medtrumPump: MedtrumPump
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = SET_PATCH.code
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRequest(): ByteArray {
|
||||||
|
/**
|
||||||
|
* byte 0: opCode
|
||||||
|
* byte 1: alarmSetting // See AlarmSetting
|
||||||
|
* byte 2-3: hourlyMaxInsulin // Max hourly dose of insulin, divided by 0.05
|
||||||
|
* byte 4-5: dailyMaxSet // Max daily dose of insulin, divided by 0.05
|
||||||
|
* byte 6: expirationTimer // Expiration timer, 0 = no expiration 1 = 12 hour reminder and expiration
|
||||||
|
* byte 7: autoSuspendEnable // Value for auto mode, not used for AAPS
|
||||||
|
* byte 8: autoSuspendTime // Value for auto mode, not used for AAPS
|
||||||
|
* byte 9: lowSuspend // Value for auto mode, not used for AAPS
|
||||||
|
* byte 10: predictiveLowSuspend // Value for auto mode, not used for AAPS
|
||||||
|
* byte 11: predictiveLowSuspendRange // Value for auto mode, not used for AAPS
|
||||||
|
*/
|
||||||
|
|
||||||
|
val alarmSetting: AlarmSetting = medtrumPump.desiredAlarmSetting
|
||||||
|
val hourlyMaxInsulin: Int = round(medtrumPump.desiredHourlyMaxInsulin / 0.05).toInt()
|
||||||
|
val dailyMaxInsulin: Int = round(medtrumPump.desiredDailyMaxInsulin / 0.05).toInt()
|
||||||
|
val patchExpiration: Byte = medtrumPump.desiredPatchExpiration.toByte()
|
||||||
|
val autoSuspendEnable: Byte = 0
|
||||||
|
val autoSuspendTime: Byte = 12 // Not sure why, but pump needs this
|
||||||
|
val lowSuspend: Byte = 0
|
||||||
|
val predictiveLowSuspend: Byte = 0
|
||||||
|
val predictiveLowSuspendRange: Byte = 30 // Not sure why, but pump needs this
|
||||||
|
|
||||||
|
return byteArrayOf(opCode) + alarmSetting.code + hourlyMaxInsulin.toByteArray(2) + dailyMaxInsulin.toByteArray(2) + patchExpiration + autoSuspendEnable + autoSuspendTime + lowSuspend + predictiveLowSuspend + predictiveLowSuspendRange
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.MedtrumPump
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_TEMP_BASAL
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.BasalType
|
||||||
|
import info.nightscout.pump.medtrum.extension.toByteArray
|
||||||
|
import info.nightscout.pump.medtrum.extension.toInt
|
||||||
|
import info.nightscout.pump.medtrum.extension.toLong
|
||||||
|
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.math.round
|
||||||
|
|
||||||
|
class SetTempBasalPacket(injector: HasAndroidInjector, private val absoluteRate: Double, private val durationInMinutes: Int) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
@Inject lateinit var medtrumPump: MedtrumPump
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val RESP_BASAL_TYPE_START = 6
|
||||||
|
private const val RESP_BASAL_TYPE_END = RESP_BASAL_TYPE_START + 1
|
||||||
|
private const val RESP_BASAL_RATE_START = RESP_BASAL_TYPE_END
|
||||||
|
private const val RESP_BASAL_RATE_END = RESP_BASAL_RATE_START + 2
|
||||||
|
private const val RESP_BASAL_SEQUENCE_START = RESP_BASAL_RATE_END
|
||||||
|
private const val RESP_BASAL_SEQUENCE_END = RESP_BASAL_SEQUENCE_START + 2
|
||||||
|
private const val RESP_BASAL_PATCH_ID_START = RESP_BASAL_SEQUENCE_END
|
||||||
|
private const val RESP_BASAL_PATCH_ID_END = RESP_BASAL_PATCH_ID_START + 2
|
||||||
|
private const val RESP_BASAL_START_TIME_START = RESP_BASAL_PATCH_ID_END
|
||||||
|
private const val RESP_BASAL_START_TIME_END = RESP_BASAL_START_TIME_START + 4
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = SET_TEMP_BASAL.code
|
||||||
|
expectedMinRespLength = RESP_BASAL_START_TIME_END
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRequest(): ByteArray {
|
||||||
|
/**
|
||||||
|
* byte 0: opCode
|
||||||
|
* byte 1: tempBasalType
|
||||||
|
* byte 2-3: tempBasalRate
|
||||||
|
* byte 4-5: tempBasalDuration
|
||||||
|
*/
|
||||||
|
val tempBasalType: Byte = 6 // Fixed to temp basal value for now
|
||||||
|
val tempBasalRate: Int = round(absoluteRate / 0.05).toInt()
|
||||||
|
val tempBasalDuration: Int = durationInMinutes
|
||||||
|
return byteArrayOf(opCode) + tempBasalType + tempBasalRate.toByteArray(2) + tempBasalDuration.toByteArray(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleResponse(data: ByteArray): Boolean {
|
||||||
|
val success = super.handleResponse(data)
|
||||||
|
if (success) {
|
||||||
|
val basalType = enumValues<BasalType>()[data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()]
|
||||||
|
val basalRate = data.copyOfRange(RESP_BASAL_RATE_START, RESP_BASAL_RATE_END).toInt() * 0.05
|
||||||
|
val basalSequence = data.copyOfRange(RESP_BASAL_SEQUENCE_START, RESP_BASAL_SEQUENCE_END).toInt()
|
||||||
|
val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toLong()
|
||||||
|
|
||||||
|
val rawTime = data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong()
|
||||||
|
val basalStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong())
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Basal status update: type=$basalType, rate=$basalRate, sequence=$basalSequence, patchId=$basalPatchId, startTime=$basalStartTime, rawTime=$rawTime")
|
||||||
|
|
||||||
|
medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalStartTime)
|
||||||
|
}
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_TIME
|
||||||
|
import info.nightscout.pump.medtrum.extension.toByteArray
|
||||||
|
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
|
||||||
|
|
||||||
|
class SetTimePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = SET_TIME.code
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRequest(): ByteArray {
|
||||||
|
val time = MedtrumTimeUtil().getCurrentTimePumpSeconds()
|
||||||
|
return byteArrayOf(opCode) + 2.toByte() + time.toByteArray(4)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.MedtrumPump
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_TIME_ZONE
|
||||||
|
import info.nightscout.pump.medtrum.extension.toByteArray
|
||||||
|
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
|
||||||
|
import info.nightscout.shared.utils.DateUtil
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SetTimeZonePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
@Inject lateinit var dateUtil: DateUtil
|
||||||
|
@Inject lateinit var medtrumPump: MedtrumPump
|
||||||
|
|
||||||
|
var offsetMins: Int = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = SET_TIME_ZONE.code
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRequest(): ByteArray {
|
||||||
|
val time = MedtrumTimeUtil().getCurrentTimePumpSeconds()
|
||||||
|
offsetMins = dateUtil.getTimeZoneOffsetMinutes(dateUtil.now())
|
||||||
|
if (offsetMins < 0) offsetMins += 65536
|
||||||
|
return byteArrayOf(opCode) + offsetMins.toByteArray(2) + time.toByteArray(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleResponse(data: ByteArray): Boolean {
|
||||||
|
val success = super.handleResponse(data)
|
||||||
|
if (success) {
|
||||||
|
medtrumPump.pumpTimeZoneOffset = offsetMins
|
||||||
|
}
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.MedtrumPump
|
||||||
|
import info.nightscout.pump.medtrum.extension.toInt
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.STOP_PATCH
|
||||||
|
import info.nightscout.pump.medtrum.extension.toLong
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class StopPatchPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
@Inject lateinit var medtrumPump: MedtrumPump
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val RESP_STOP_SEQUENCE_START = 6
|
||||||
|
private const val RESP_STOP_SEQUENCE_END = RESP_STOP_SEQUENCE_START + 2
|
||||||
|
private const val RESP_STOP_PATCH_ID_START = RESP_STOP_SEQUENCE_END
|
||||||
|
private const val RESP_STOP_PATCH_ID_END = RESP_STOP_PATCH_ID_START + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = STOP_PATCH.code
|
||||||
|
expectedMinRespLength = RESP_STOP_PATCH_ID_END
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleResponse(data: ByteArray): Boolean {
|
||||||
|
val success = super.handleResponse(data)
|
||||||
|
if (success) {
|
||||||
|
val stopSequence = data.copyOfRange(RESP_STOP_SEQUENCE_START, RESP_STOP_SEQUENCE_END).toInt()
|
||||||
|
val stopPatchId = data.copyOfRange(RESP_STOP_PATCH_ID_START, RESP_STOP_PATCH_ID_END).toLong()
|
||||||
|
|
||||||
|
medtrumPump.handleStopStatusUpdate(stopSequence, stopPatchId)
|
||||||
|
}
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.SUBSCRIBE
|
||||||
|
import info.nightscout.pump.medtrum.extension.toByteArray
|
||||||
|
|
||||||
|
class SubscribePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = SUBSCRIBE.code
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRequest(): ByteArray {
|
||||||
|
return byteArrayOf(opCode) + 4095.toByteArray(2)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package info.nightscout.pump.medtrum.comm.packets
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.MedtrumPump
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.CommandType.SYNCHRONIZE
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
|
||||||
|
import info.nightscout.pump.medtrum.extension.toByteArray
|
||||||
|
import info.nightscout.pump.medtrum.extension.toInt
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SynchronizePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
|
||||||
|
|
||||||
|
@Inject lateinit var medtrumPump: MedtrumPump
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val RESP_STATE_START = 6
|
||||||
|
private const val RESP_STATE_END = RESP_STATE_START + 1
|
||||||
|
private const val RESP_FIELDS_START = 7
|
||||||
|
private const val RESP_FIELDS_END = RESP_FIELDS_START + 2
|
||||||
|
private const val RESP_SYNC_DATA_START = 9
|
||||||
|
|
||||||
|
private const val MASK_SUSPEND = 0x01
|
||||||
|
private const val MASK_NORMAL_BOLUS = 0x02
|
||||||
|
private const val MASK_EXTENDED_BOLUS = 0x04
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
opCode = SYNCHRONIZE.code
|
||||||
|
expectedMinRespLength = RESP_SYNC_DATA_START + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleResponse(data: ByteArray): Boolean {
|
||||||
|
val success = super.handleResponse(data)
|
||||||
|
if (success) {
|
||||||
|
var state = MedtrumPumpState.fromByte(data[RESP_STATE_START])
|
||||||
|
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "SynchronizePacket: state: $state")
|
||||||
|
if (state != medtrumPump.pumpState) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "State changed from ${medtrumPump.pumpState} to $state")
|
||||||
|
medtrumPump.pumpState = state
|
||||||
|
}
|
||||||
|
|
||||||
|
var fieldMask = data.copyOfRange(RESP_FIELDS_START, RESP_FIELDS_END).toInt()
|
||||||
|
var syncData = data.copyOfRange(RESP_SYNC_DATA_START, data.size)
|
||||||
|
var offset = 0
|
||||||
|
|
||||||
|
if (fieldMask != 0) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "SynchronizePacket: fieldMask: $fieldMask")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove bolus fields from fieldMask if fields are present (we sync bolus trough other commands)
|
||||||
|
if (fieldMask and MASK_SUSPEND != 0) {
|
||||||
|
offset += 4 // If field is present, skip 4 bytes
|
||||||
|
}
|
||||||
|
if (fieldMask and MASK_NORMAL_BOLUS != 0) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "SynchronizePacket: Normal bolus present removing from fieldMask")
|
||||||
|
fieldMask = fieldMask and MASK_NORMAL_BOLUS.inv()
|
||||||
|
syncData = syncData.copyOfRange(0, offset) + syncData.copyOfRange(offset + 3, syncData.size)
|
||||||
|
}
|
||||||
|
if (fieldMask and MASK_EXTENDED_BOLUS != 0) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "SynchronizePacket: Extended bolus present removing from fieldMask")
|
||||||
|
fieldMask = fieldMask and MASK_EXTENDED_BOLUS.inv()
|
||||||
|
syncData = syncData.copyOfRange(0, offset) + syncData.copyOfRange(offset + 3, syncData.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let the notification packet handle the rest of the sync data
|
||||||
|
NotificationPacket(injector).handleMaskedMessage(fieldMask.toByteArray(2) + syncData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package info.nightscout.pump.medtrum.di
|
||||||
|
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.android.ContributesAndroidInjector
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.ActivatePacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.AuthorizePacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.CancelBolusPacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.CancelTempBasalPacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.ClearPumpAlarmPacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.GetDeviceTypePacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.GetRecordPacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.GetTimePacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.MedtrumPacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.NotificationPacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.PollPatchPacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.PrimePacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.ReadBolusStatePacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.ResumePumpPacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.SetBasalProfilePacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.SetBolusMotorPacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.SetBolusPacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.SetPatchPacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.SetTempBasalPacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.SetTimePacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.SetTimeZonePacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.StopPatchPacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.SubscribePacket
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.SynchronizePacket
|
||||||
|
|
||||||
|
@Module
|
||||||
|
abstract class MedtrumCommModule {
|
||||||
|
|
||||||
|
@ContributesAndroidInjector abstract fun contributesActivatePacket(): ActivatePacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesAuthorizePacket(): AuthorizePacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesCancelBolusPacket(): CancelBolusPacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesCancelTempBasalPacket(): CancelTempBasalPacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesClearPumpAlarmPacket(): ClearPumpAlarmPacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesGetDeviceTypePacket(): GetDeviceTypePacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesGetRecordPacket(): GetRecordPacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesGetTimePacket(): GetTimePacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesMedtrumPacket(): MedtrumPacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesNotificationPacket(): NotificationPacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesPollPatchPacket(): PollPatchPacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesPrimePacket(): PrimePacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesReadBolusStatePacket(): ReadBolusStatePacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesResumePumpPacket(): ResumePumpPacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesSetBasalProfilePacket(): SetBasalProfilePacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesSetBolusMotorPacket(): SetBolusMotorPacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesSetBolusPacket(): SetBolusPacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesSetPatchPacket(): SetPatchPacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesSetTempBasalPacket(): SetTempBasalPacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesSetTimePacket(): SetTimePacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesSetTimeZonePacket(): SetTimeZonePacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesStopPatchPacket(): StopPatchPacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesSubscribePacket(): SubscribePacket
|
||||||
|
@ContributesAndroidInjector abstract fun contributesSynchronizePacket(): SynchronizePacket
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package info.nightscout.pump.medtrum.di
|
||||||
|
|
||||||
|
import javax.inject.Qualifier
|
||||||
|
import javax.inject.Scope
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
annotation class MedtrumPluginQualifier
|
||||||
|
|
||||||
|
@MustBeDocumented
|
||||||
|
@Scope
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
annotation class FragmentScope
|
|
@ -0,0 +1,121 @@
|
||||||
|
package info.nightscout.pump.medtrum.di
|
||||||
|
|
||||||
|
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.pump.medtrum.services.MedtrumService
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumActivateCompleteFragment
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumActivateFragment
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumAttachPatchFragment
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumDeactivatePatchFragment
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumDeactivationCompleteFragment
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumPreparePatchConnectFragment
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumPreparePatchFragment
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumPrimeCompleteFragment
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumPrimeFragment
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumPrimingFragment
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumStartDeactivationFragment
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumActivity
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumOverviewFragment
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumRetryActivationConnectFragment
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumRetryActivationFragment
|
||||||
|
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumOverviewViewModel
|
||||||
|
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
|
||||||
|
import info.nightscout.pump.medtrum.ui.viewmodel.ViewModelFactory
|
||||||
|
import info.nightscout.pump.medtrum.ui.viewmodel.ViewModelKey
|
||||||
|
import javax.inject.Provider
|
||||||
|
|
||||||
|
@Module(includes = [MedtrumCommModule::class])
|
||||||
|
@Suppress("unused")
|
||||||
|
abstract class MedtrumModule {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@MedtrumPluginQualifier
|
||||||
|
fun providesViewModelFactory(@MedtrumPluginQualifier viewModels: MutableMap<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>): ViewModelProvider.Factory {
|
||||||
|
return ViewModelFactory(viewModels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIEW MODELS
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@MedtrumPluginQualifier
|
||||||
|
@ViewModelKey(MedtrumOverviewViewModel::class)
|
||||||
|
internal abstract fun bindsMedtrumOverviewViewmodel(viewModel: MedtrumOverviewViewModel): ViewModel
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@MedtrumPluginQualifier
|
||||||
|
@ViewModelKey(MedtrumViewModel::class)
|
||||||
|
internal abstract fun bindsMedtrumViewModel(viewModel: MedtrumViewModel): ViewModel
|
||||||
|
|
||||||
|
// FRAGMENTS
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract fun contributesMedtrumOverviewFragment(): MedtrumOverviewFragment
|
||||||
|
|
||||||
|
@FragmentScope
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
internal abstract fun contributesStartDeactivationFragment(): MedtrumStartDeactivationFragment
|
||||||
|
|
||||||
|
@FragmentScope
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
internal abstract fun contributesDeactivatePatchFragment(): MedtrumDeactivatePatchFragment
|
||||||
|
|
||||||
|
@FragmentScope
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
internal abstract fun contributesDeactivationCompleteFragment(): MedtrumDeactivationCompleteFragment
|
||||||
|
|
||||||
|
@FragmentScope
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
internal abstract fun contributesPreparePatchFragment(): MedtrumPreparePatchFragment
|
||||||
|
|
||||||
|
@FragmentScope
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
internal abstract fun contributesPreparePatchConnectFragment(): MedtrumPreparePatchConnectFragment
|
||||||
|
|
||||||
|
@FragmentScope
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
internal abstract fun contributesRetryActivationFragment(): MedtrumRetryActivationFragment
|
||||||
|
@FragmentScope
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
internal abstract fun contributesRetryActivationConnectFragment(): MedtrumRetryActivationConnectFragment
|
||||||
|
|
||||||
|
@FragmentScope
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
internal abstract fun contributesPrimeFragment(): MedtrumPrimeFragment
|
||||||
|
|
||||||
|
@FragmentScope
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
internal abstract fun contributesPrimeCompleteFragment(): MedtrumPrimeCompleteFragment
|
||||||
|
|
||||||
|
@FragmentScope
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
internal abstract fun contributesPrimingFragment(): MedtrumPrimingFragment
|
||||||
|
|
||||||
|
@FragmentScope
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
internal abstract fun contributesAttachPatchFragment(): MedtrumAttachPatchFragment
|
||||||
|
|
||||||
|
@FragmentScope
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
internal abstract fun contributesActivateFragment(): MedtrumActivateFragment
|
||||||
|
|
||||||
|
@FragmentScope
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
internal abstract fun contributesActivateCompleteFragment(): MedtrumActivateCompleteFragment
|
||||||
|
|
||||||
|
// ACTIVITIES
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract fun contributesMedtrumActivity(): MedtrumActivity
|
||||||
|
|
||||||
|
// SERVICE
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract fun contributesMedtrumService(): MedtrumService
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package info.nightscout.pump.medtrum.encryption
|
||||||
|
|
||||||
|
import info.nightscout.pump.medtrum.extension.toByteArray
|
||||||
|
import info.nightscout.pump.medtrum.extension.toLong
|
||||||
|
|
||||||
|
class Crypt {
|
||||||
|
|
||||||
|
private val RIJNDEAL_S_BOX: IntArray = intArrayOf(99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118, 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192, 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216, 49, 21, 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117, 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132, 83, 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207, 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186, 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, 140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22)
|
||||||
|
private val RIJNDEAL_INVERSE_S_BOX: IntArray = intArrayOf(82, 9, 106, 213, 48, 54, 165, 56, 191, 64, 163, 158, 129, 243, 215, 251, 124, 227, 57, 130, 155, 47, 255, 135, 52, 142, 67, 68, 196, 222, 233, 203, 84, 123, 148, 50, 166, 194, 35, 61, 238, 76, 149, 11, 66, 250, 195, 78, 8, 46, 161, 102, 40, 217, 36, 178, 118, 91, 162, 73, 109, 139, 209, 37, 114, 248, 246, 100, 134, 104, 152, 22, 212, 164, 92, 204, 93, 101, 182, 146, 108, 112, 72, 80, 253, 237, 185, 218, 94, 21, 70, 87, 167, 141, 157, 132, 144, 216, 171, 0, 140, 188, 211, 10, 247, 228, 88, 5, 184, 179, 69, 6, 208, 44, 30, 143, 202, 63, 15, 2, 193, 175, 189, 3, 1, 19, 138, 107, 58, 145, 17, 65, 79, 103, 220, 234, 151, 242, 207, 206, 240, 180, 230, 115, 150, 172, 116, 34, 231, 173, 53, 133, 226, 249, 55, 232, 28, 117, 223, 110, 71, 241, 26, 113, 29, 41, 197, 137, 111, 183, 98, 14, 170, 24, 190, 27, 252, 86, 62, 75, 198, 210, 121, 32, 154, 219, 192, 254, 120, 205, 90, 244, 31, 221, 168, 51, 136, 7, 199, 49, 177, 18, 16, 89, 39, 128, 236, 95, 96, 81, 127, 169, 25, 181, 74, 13, 45, 229, 122, 159, 147, 201, 156, 239, 160, 224, 59, 77, 174, 42, 245, 176, 200, 235, 187, 60, 131, 83, 153, 97, 23, 43, 4, 126, 186, 119, 214, 38, 225, 105, 20, 99, 85, 33, 12, 125)
|
||||||
|
|
||||||
|
val MED_CIPHER: Long = 1344751489
|
||||||
|
|
||||||
|
fun keyGen(input: Long): Long {
|
||||||
|
val key = randomGen(randomGen(MED_CIPHER xor input))
|
||||||
|
return simpleCrypt(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateRandomToken(): Long {
|
||||||
|
val randomBytes = ByteArray(4)
|
||||||
|
val random = java.security.SecureRandom()
|
||||||
|
random.nextBytes(randomBytes)
|
||||||
|
return randomBytes.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun simpleCrypt(inputData: Long): Long {
|
||||||
|
var temp = inputData xor MED_CIPHER
|
||||||
|
for (i in 0 until 32) {
|
||||||
|
temp = changeByTable(rotatoLeft(temp, 32, 1), RIJNDEAL_S_BOX).toLong()
|
||||||
|
}
|
||||||
|
return temp
|
||||||
|
}
|
||||||
|
|
||||||
|
fun simpleDecrypt(inputData: Long): Long {
|
||||||
|
var temp = inputData
|
||||||
|
for (i in 0 until 32) {
|
||||||
|
temp = rotatoRight(changeByTable(temp, RIJNDEAL_INVERSE_S_BOX), 32, 1).toLong()
|
||||||
|
}
|
||||||
|
return temp xor MED_CIPHER
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun randomGen(input: Long): Long {
|
||||||
|
val A = 16807
|
||||||
|
val Q = 127773
|
||||||
|
val R = 2836
|
||||||
|
val tmp1 = input / Q
|
||||||
|
var ret = (input - (tmp1 * Q)) * A - (tmp1 * R)
|
||||||
|
if (ret < 0) {
|
||||||
|
ret += 2147483647L
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun changeByTable(inputData: Long, tableData: IntArray): Long {
|
||||||
|
val value = inputData.toByteArray(4)
|
||||||
|
val results = ByteArray(4)
|
||||||
|
|
||||||
|
for (i in value.indices) {
|
||||||
|
var byte = value[i].toInt()
|
||||||
|
if (byte < 0) {
|
||||||
|
byte += 256
|
||||||
|
}
|
||||||
|
results[i] = tableData[byte].toByte()
|
||||||
|
}
|
||||||
|
return results.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rotatoLeft(x: Long, s: Int, n: Int): Long {
|
||||||
|
return (x shl n) or (x ushr (s - n))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rotatoRight(x: Long, s: Int, n: Int): Int {
|
||||||
|
return (x ushr n or (x shl (s - n))).toInt()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package info.nightscout.pump.medtrum.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()
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package info.nightscout.pump.medtrum.extension
|
||||||
|
|
||||||
|
fun Boolean.toByte(): Byte {
|
||||||
|
return if (this == true)
|
||||||
|
0x1
|
||||||
|
else
|
||||||
|
0x0
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package info.nightscout.pump.medtrum.extension
|
||||||
|
|
||||||
|
/** Extensions for different types of conversions needed when doing stuff with bytes */
|
||||||
|
fun ByteArray.toLong(): Long {
|
||||||
|
require(this.size <= 8) {
|
||||||
|
"Array size must be <= 8 for 'toLong' conversion operation"
|
||||||
|
}
|
||||||
|
var result = 0L
|
||||||
|
for (i in this.indices) {
|
||||||
|
val byte = this[i]
|
||||||
|
val shifted = (byte.toInt() and 0xFF).toLong() shl 8 * i
|
||||||
|
result = result or shifted
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ByteArray.toInt(): Int {
|
||||||
|
require(this.size <= 4) {
|
||||||
|
"Array size must be <= 4 for 'toInt' conversion operation"
|
||||||
|
}
|
||||||
|
var result = 0
|
||||||
|
for (i in this.indices) {
|
||||||
|
val byte = this[i]
|
||||||
|
val shifted = (byte.toInt() and 0xFF).toInt() shl 8 * i
|
||||||
|
result = result or shifted
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ByteArray.toFloat(): Float {
|
||||||
|
require(this.size == 4) {
|
||||||
|
"Array size must be == 4 for 'toFloat' conversion operation"
|
||||||
|
}
|
||||||
|
var asInt = 0
|
||||||
|
for (i in this.indices) {
|
||||||
|
val byte = this[i]
|
||||||
|
val shifted = (byte.toInt() and 0xFF) shl 8 * i
|
||||||
|
asInt = asInt or shifted
|
||||||
|
}
|
||||||
|
return Float.fromBits(asInt)
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package info.nightscout.pump.medtrum.extension
|
||||||
|
|
||||||
|
/** Extensions for different types of conversions needed when doing stuff with bytes */
|
||||||
|
fun Int.toByteArray(byteLength: Int): ByteArray {
|
||||||
|
val bytes = ByteArray(byteLength)
|
||||||
|
for (i in 0 until byteLength) {
|
||||||
|
bytes[i] = (this shr (i * 8) and 0xFF).toByte()
|
||||||
|
}
|
||||||
|
return bytes
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package info.nightscout.pump.medtrum.extension
|
||||||
|
|
||||||
|
/** Extensions for different types of conversions needed when doing stuff with bytes */
|
||||||
|
fun Long.toByteArray(byteLength: Int): ByteArray {
|
||||||
|
val bytes = ByteArray(byteLength)
|
||||||
|
for (i in 0 until byteLength) {
|
||||||
|
bytes[i] = (this shr (i * 8) and 0xFF).toByte()
|
||||||
|
}
|
||||||
|
return bytes
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package info.nightscout.pump.medtrum.extension
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
|
||||||
|
fun View?.visible() = this?.run { visibility = View.VISIBLE }
|
||||||
|
|
||||||
|
fun View?.visible(vararg views: View?) {
|
||||||
|
visible()
|
||||||
|
for (view in views)
|
||||||
|
view.visible()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View?.invisible() = this?.run { visibility = View.INVISIBLE }
|
||||||
|
|
||||||
|
fun View?.invisible(vararg views: View?) {
|
||||||
|
invisible()
|
||||||
|
for (view in views)
|
||||||
|
view.invisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View?.gone() = this?.run { visibility = View.GONE }
|
||||||
|
|
||||||
|
fun View?.gone(vararg views: View?) {
|
||||||
|
gone()
|
||||||
|
for (view in views)
|
||||||
|
view.gone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View?.setVisibleOrGone(visibleOrGone: Boolean, vararg views: View?) {
|
||||||
|
for (view in views)
|
||||||
|
if (visibleOrGone) view.visible() else view.gone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View?.setVisibleOrGone(visibleOrGone: Boolean) = setVisibleOrGone(visibleOrGone, this)
|
|
@ -0,0 +1,475 @@
|
||||||
|
package info.nightscout.pump.medtrum.services
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.bluetooth.BluetoothAdapter
|
||||||
|
import android.bluetooth.BluetoothDevice
|
||||||
|
import android.bluetooth.BluetoothGatt
|
||||||
|
import android.bluetooth.BluetoothGattCallback
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic
|
||||||
|
import android.bluetooth.BluetoothGattDescriptor
|
||||||
|
import android.bluetooth.BluetoothGattService
|
||||||
|
import android.bluetooth.BluetoothManager
|
||||||
|
import android.bluetooth.BluetoothProfile
|
||||||
|
import android.bluetooth.le.ScanCallback
|
||||||
|
import android.bluetooth.le.ScanResult
|
||||||
|
import android.bluetooth.le.ScanSettings
|
||||||
|
import android.bluetooth.le.ScanFilter
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.HandlerThread
|
||||||
|
import android.os.SystemClock
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import info.nightscout.core.ui.toast.ToastUtils
|
||||||
|
import info.nightscout.pump.medtrum.extension.toInt
|
||||||
|
import info.nightscout.pump.medtrum.comm.WriteCommandPackets
|
||||||
|
import info.nightscout.pump.medtrum.comm.ManufacturerData
|
||||||
|
import info.nightscout.pump.medtrum.comm.ReadDataPacket
|
||||||
|
import info.nightscout.rx.logging.AAPSLogger
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import java.util.UUID
|
||||||
|
import java.util.Arrays
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
interface BLECommCallback {
|
||||||
|
|
||||||
|
fun onBLEConnected()
|
||||||
|
fun onBLEDisconnected()
|
||||||
|
fun onNotification(notification: ByteArray)
|
||||||
|
fun onIndication(indication: ByteArray)
|
||||||
|
fun onSendMessageError(reason: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class BLEComm @Inject internal constructor(
|
||||||
|
private val aapsLogger: AAPSLogger,
|
||||||
|
private val context: Context
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val WRITE_DELAY_MILLIS: Long = 10
|
||||||
|
private const val SERVICE_UUID = "669A9001-0008-968F-E311-6050405558B3"
|
||||||
|
private const val READ_UUID = "669a9120-0008-968f-e311-6050405558b3"
|
||||||
|
private const val WRITE_UUID = "669a9101-0008-968f-e311-6050405558b3"
|
||||||
|
private const val CONFIG_UUID = "00002902-0000-1000-8000-00805f9b34fb"
|
||||||
|
|
||||||
|
private const val NEEDS_ENABLE_NOTIFICATION = 0x10
|
||||||
|
private const val NEEDS_ENABLE_INDICATION = 0x20
|
||||||
|
private const val NEEDS_ENABLE = 0x30
|
||||||
|
|
||||||
|
private const val MANUFACTURER_ID = 18305
|
||||||
|
}
|
||||||
|
|
||||||
|
private val handler =
|
||||||
|
Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper)
|
||||||
|
private val mBluetoothAdapter: BluetoothAdapter? get() = (context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?)?.adapter
|
||||||
|
private var mBluetoothGatt: BluetoothGatt? = null
|
||||||
|
|
||||||
|
private var isConnected = false // Only to track internal ble state
|
||||||
|
private var isConnecting = false // Only to track internal ble state
|
||||||
|
private var uartWrite: BluetoothGattCharacteristic? = null
|
||||||
|
private var uartRead: BluetoothGattCharacteristic? = null
|
||||||
|
|
||||||
|
// Read and write buffers
|
||||||
|
private var mWritePackets: WriteCommandPackets? = null
|
||||||
|
private var mWriteSequenceNumber: Int = 0
|
||||||
|
private var mReadPacket: ReadDataPacket? = null
|
||||||
|
private val readLock = Any()
|
||||||
|
|
||||||
|
private var mDeviceSN: Long = 0
|
||||||
|
private var mCallback: BLECommCallback? = null
|
||||||
|
private var mDevice: BluetoothDevice? = null
|
||||||
|
|
||||||
|
fun setCallback(callback: BLECommCallback?) {
|
||||||
|
this.mCallback = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Connect flow: 1. Start scanning for our device (SN entered in settings) */
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
@Synchronized
|
||||||
|
fun startScan(): Boolean {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
|
||||||
|
ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
ToastUtils.errorToast(context, context.getString(info.nightscout.core.ui.R.string.need_connect_permission))
|
||||||
|
aapsLogger.error(LTag.PUMPBTCOMM, "missing permissions")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Start scan!!")
|
||||||
|
val settings = ScanSettings.Builder()
|
||||||
|
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
|
||||||
|
.build()
|
||||||
|
val filters = mutableListOf<ScanFilter>()
|
||||||
|
|
||||||
|
// Find our Medtrum Device!
|
||||||
|
filters.add(
|
||||||
|
ScanFilter.Builder().setDeviceName("MT").build()
|
||||||
|
)
|
||||||
|
mBluetoothAdapter?.bluetoothLeScanner?.startScan(filters, settings, mScanCallback)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
@Synchronized
|
||||||
|
fun stopScan() {
|
||||||
|
mBluetoothAdapter?.bluetoothLeScanner?.stopScan(mScanCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun connect(from: String, deviceSN: Long): Boolean {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
|
||||||
|
ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
ToastUtils.errorToast(context, context.getString(info.nightscout.core.ui.R.string.need_connect_permission))
|
||||||
|
aapsLogger.error(LTag.PUMPBTCOMM, "missing permission: $from")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Initializing BLEComm.")
|
||||||
|
if (mBluetoothAdapter == null) {
|
||||||
|
aapsLogger.error("Unable to obtain a BluetoothAdapter.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
isConnected = false
|
||||||
|
isConnecting = true
|
||||||
|
mWritePackets = null
|
||||||
|
mReadPacket = null
|
||||||
|
|
||||||
|
if (mDevice != null && mDeviceSN == deviceSN) {
|
||||||
|
// Skip scanning and directly connect to gatt
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Skipping scan and directly connecting to gatt")
|
||||||
|
connectGatt(mDevice!!)
|
||||||
|
} else {
|
||||||
|
// Scan for device
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Scanning for device")
|
||||||
|
mDeviceSN = deviceSN
|
||||||
|
startScan()
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Connect flow: 2. When device is found this is called by onScanResult() */
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
@Synchronized
|
||||||
|
private fun connectGatt(device: BluetoothDevice) {
|
||||||
|
// Reset sequence counter
|
||||||
|
mWriteSequenceNumber = 0
|
||||||
|
if (mBluetoothGatt == null) {
|
||||||
|
mBluetoothGatt = device.connectGatt(context, false, mGattCallback, BluetoothDevice.TRANSPORT_LE)
|
||||||
|
} else {
|
||||||
|
// Already connected?, this should not happen force disconnect
|
||||||
|
aapsLogger.error(LTag.PUMPBTCOMM, "connectGatt, mBluetoothGatt is not null")
|
||||||
|
disconnect("connectGatt, mBluetoothGatt is not null")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
@Synchronized
|
||||||
|
fun disconnect(from: String) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
|
||||||
|
ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
aapsLogger.error(LTag.PUMPBTCOMM, "missing permission: $from")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "disconnect from: $from")
|
||||||
|
if (isConnecting) {
|
||||||
|
isConnecting = false
|
||||||
|
stopScan()
|
||||||
|
SystemClock.sleep(100)
|
||||||
|
}
|
||||||
|
if (isConnected) {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Connected, disconnecting")
|
||||||
|
mBluetoothGatt?.disconnect()
|
||||||
|
} else {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Not connected, closing gatt")
|
||||||
|
close()
|
||||||
|
isConnected = false
|
||||||
|
mCallback?.onBLEDisconnected()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
@Synchronized fun close() {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "BluetoothAdapter close")
|
||||||
|
mBluetoothGatt?.close()
|
||||||
|
SystemClock.sleep(100)
|
||||||
|
mBluetoothGatt = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Scan callback */
|
||||||
|
private val mScanCallback: ScanCallback = object : ScanCallback() {
|
||||||
|
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "OnScanResult!" + result)
|
||||||
|
super.onScanResult(callbackType, result)
|
||||||
|
|
||||||
|
val manufacturerData =
|
||||||
|
result.scanRecord?.getManufacturerSpecificData(MANUFACTURER_ID)
|
||||||
|
?.let { ManufacturerData(it) }
|
||||||
|
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Found deviceSN: " + manufacturerData?.getDeviceSN())
|
||||||
|
|
||||||
|
if (manufacturerData?.getDeviceSN() == mDeviceSN) {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Found our device! deviceSN: " + manufacturerData.getDeviceSN())
|
||||||
|
stopScan()
|
||||||
|
mDevice = result.device
|
||||||
|
connectGatt(result.device)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onScanFailed(errorCode: Int) {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Scan FAILED!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
||||||
|
private val mGattCallback: BluetoothGattCallback = object : BluetoothGattCallback() {
|
||||||
|
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
||||||
|
onConnectionStateChangeSynchronized(gatt, status, newState) // call it synchronized
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "onServicesDiscovered")
|
||||||
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
findCharacteristic()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "onCharacteristicRead data: " + characteristic.value.contentToString() + " UUID: " + characteristic.getUuid().toString() + " status: " + status)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "onCharacteristicChanged data: " + characteristic.value.contentToString() + " UUID: " + characteristic.getUuid().toString())
|
||||||
|
|
||||||
|
val value = characteristic.getValue()
|
||||||
|
if (characteristic.getUuid() == UUID.fromString(READ_UUID)) {
|
||||||
|
mCallback?.onNotification(value)
|
||||||
|
} else if (characteristic.getUuid() == UUID.fromString(WRITE_UUID)) {
|
||||||
|
synchronized(readLock) {
|
||||||
|
if (mReadPacket == null) {
|
||||||
|
mReadPacket = ReadDataPacket(value)
|
||||||
|
} else {
|
||||||
|
mReadPacket?.addData(value)
|
||||||
|
}
|
||||||
|
if (mReadPacket?.allDataReceived() == true) {
|
||||||
|
mReadPacket?.getData()?.let { mCallback?.onIndication(it) }
|
||||||
|
mReadPacket = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "onCharacteristicWrite status = " + status)
|
||||||
|
|
||||||
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
// Check if we need to finish our command!
|
||||||
|
mWritePackets?.let {
|
||||||
|
synchronized(it) {
|
||||||
|
val value: ByteArray? = mWritePackets?.getNextPacket()
|
||||||
|
if (value != null) {
|
||||||
|
writeCharacteristic(uartWriteBTGattChar, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mCallback?.onSendMessageError("onCharacteristicWrite failure")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDescriptorWrite(gatt: BluetoothGatt?, descriptor: BluetoothGattDescriptor?, status: Int) {
|
||||||
|
super.onDescriptorWrite(gatt, descriptor, status)
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "onDescriptorWrite " + status)
|
||||||
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
readDescriptor(descriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Connect flow: 5. Notifications enabled read descriptor to verify and start auth process*/
|
||||||
|
override fun onDescriptorRead(
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
descriptor: BluetoothGattDescriptor,
|
||||||
|
status: Int
|
||||||
|
) {
|
||||||
|
super.onDescriptorRead(gatt, descriptor, status)
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "onDescriptorRead status: " + status)
|
||||||
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
checkDescriptor(descriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
@Synchronized
|
||||||
|
private fun readDescriptor(descriptor: BluetoothGattDescriptor?) {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "readDescriptor")
|
||||||
|
if (mBluetoothAdapter == null || mBluetoothGatt == null || descriptor == null) {
|
||||||
|
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
|
||||||
|
isConnecting = false
|
||||||
|
isConnected = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mBluetoothGatt?.readDescriptor(descriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
private fun checkDescriptor(descriptor: BluetoothGattDescriptor) {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "checkDescriptor")
|
||||||
|
val service = getGattService()
|
||||||
|
if (mBluetoothAdapter == null || mBluetoothGatt == null || service == null) {
|
||||||
|
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
|
||||||
|
isConnecting = false
|
||||||
|
isConnected = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (descriptor.value.toInt() > 0) {
|
||||||
|
var notificationEnabled = true
|
||||||
|
val characteristics = service.characteristics
|
||||||
|
for (j in 0 until characteristics.size) {
|
||||||
|
val configDescriptor =
|
||||||
|
characteristics[j].getDescriptor(UUID.fromString(CONFIG_UUID))
|
||||||
|
if (configDescriptor.value == null || configDescriptor.value.toInt() <= 0) {
|
||||||
|
notificationEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (notificationEnabled) {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Notifications enabled!")
|
||||||
|
/** Connect flow: 6. Connected */
|
||||||
|
mCallback?.onBLEConnected()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
@Synchronized
|
||||||
|
private fun setCharacteristicNotification(characteristic: BluetoothGattCharacteristic?, enabled: Boolean) {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "setCharacteristicNotification")
|
||||||
|
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
|
||||||
|
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
|
||||||
|
isConnecting = false
|
||||||
|
isConnected = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mBluetoothGatt?.setCharacteristicNotification(characteristic, enabled)
|
||||||
|
characteristic?.getDescriptor(UUID.fromString(CONFIG_UUID))?.let {
|
||||||
|
if (characteristic.properties and NEEDS_ENABLE_NOTIFICATION > 0) {
|
||||||
|
it.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
||||||
|
mBluetoothGatt?.writeDescriptor(it)
|
||||||
|
} else if (characteristic.properties and NEEDS_ENABLE_INDICATION > 0) {
|
||||||
|
it.value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
|
||||||
|
mBluetoothGatt?.writeDescriptor(it)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Connect flow: 3. When we are connected discover services*/
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
@Synchronized
|
||||||
|
private fun onConnectionStateChangeSynchronized(gatt: BluetoothGatt, status: Int, newState: Int) {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "onConnectionStateChange newState: " + newState + " status: " + status)
|
||||||
|
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||||
|
isConnected = true
|
||||||
|
isConnecting = false
|
||||||
|
mBluetoothGatt?.discoverServices()
|
||||||
|
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||||
|
if (isConnecting) {
|
||||||
|
// When we are disconnected during connecting, we reset the device address to force a new scan
|
||||||
|
aapsLogger.warn(LTag.PUMPBTCOMM, "Disconnected while connecting! Reset device address")
|
||||||
|
mDevice = null
|
||||||
|
// Wait a bit before retrying
|
||||||
|
SystemClock.sleep(2000)
|
||||||
|
}
|
||||||
|
close()
|
||||||
|
isConnected = false
|
||||||
|
isConnecting = false
|
||||||
|
mCallback?.onBLEDisconnected()
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Device was disconnected " + gatt.device.name) //Device was disconnected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun sendMessage(message: ByteArray) {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "sendMessage message = " + Arrays.toString(message))
|
||||||
|
if (mWritePackets?.allPacketsConsumed() == false) {
|
||||||
|
aapsLogger.error(LTag.PUMPBTCOMM, "sendMessage not all packets consumed!! unable to sent message!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mWritePackets = WriteCommandPackets(message, mWriteSequenceNumber)
|
||||||
|
mWriteSequenceNumber = (mWriteSequenceNumber + 1) % 256
|
||||||
|
val value: ByteArray? = mWritePackets?.getNextPacket()
|
||||||
|
if (value != null) {
|
||||||
|
writeCharacteristic(uartWriteBTGattChar, value)
|
||||||
|
} else {
|
||||||
|
aapsLogger.error(LTag.PUMPBTCOMM, "sendMessage error in writePacket!")
|
||||||
|
mCallback?.onSendMessageError("error in writePacket!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getGattService(): BluetoothGattService? {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "getGattService")
|
||||||
|
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
|
||||||
|
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
|
||||||
|
isConnecting = false
|
||||||
|
isConnected = false
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return mBluetoothGatt?.getService(UUID.fromString(SERVICE_UUID))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
@Synchronized
|
||||||
|
private fun writeCharacteristic(characteristic: BluetoothGattCharacteristic, data: ByteArray?) {
|
||||||
|
handler.postDelayed({
|
||||||
|
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
|
||||||
|
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
|
||||||
|
isConnecting = false
|
||||||
|
isConnected = false
|
||||||
|
} else {
|
||||||
|
characteristic.value = data
|
||||||
|
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "writeCharacteristic: ${Arrays.toString(data)}")
|
||||||
|
val success = mBluetoothGatt?.writeCharacteristic(characteristic)
|
||||||
|
if (success != true) {
|
||||||
|
mCallback?.onSendMessageError("Failed to write characteristic")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, WRITE_DELAY_MILLIS)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val uartWriteBTGattChar: BluetoothGattCharacteristic
|
||||||
|
get() = uartWrite
|
||||||
|
?: BluetoothGattCharacteristic(UUID.fromString(WRITE_UUID), BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT, 0).also { uartWrite = it }
|
||||||
|
|
||||||
|
/** Connect flow: 4. When services are discovered find characteristics and set notifications*/
|
||||||
|
private fun findCharacteristic() {
|
||||||
|
val gattService = getGattService() ?: return
|
||||||
|
var uuid: String
|
||||||
|
val gattCharacteristics = gattService.characteristics
|
||||||
|
for (i in 0..gattCharacteristics.size - 1) {
|
||||||
|
val gattCharacteristic = gattCharacteristics.get(i)
|
||||||
|
// Check whether read or write properties is set, the pump needs us to enable notifications on all characteristics that have these properties
|
||||||
|
if (gattCharacteristic.properties and NEEDS_ENABLE > 0) {
|
||||||
|
handler.postDelayed({
|
||||||
|
uuid = gattCharacteristic.uuid.toString()
|
||||||
|
setCharacteristicNotification(gattCharacteristic, true)
|
||||||
|
if (READ_UUID == uuid) {
|
||||||
|
uartRead = gattCharacteristic
|
||||||
|
}
|
||||||
|
if (WRITE_UUID == uuid) {
|
||||||
|
uartWrite = gattCharacteristic
|
||||||
|
}
|
||||||
|
}, (i * 600).toLong())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,980 @@
|
||||||
|
package info.nightscout.pump.medtrum.services
|
||||||
|
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Binder
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.os.SystemClock
|
||||||
|
import dagger.android.DaggerService
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.core.utils.fabric.FabricPrivacy
|
||||||
|
import info.nightscout.interfaces.constraints.Constraints
|
||||||
|
import info.nightscout.interfaces.notifications.Notification
|
||||||
|
import info.nightscout.interfaces.plugin.ActivePlugin
|
||||||
|
import info.nightscout.interfaces.profile.Profile
|
||||||
|
import info.nightscout.interfaces.profile.ProfileFunction
|
||||||
|
import info.nightscout.interfaces.pump.DetailedBolusInfo
|
||||||
|
import info.nightscout.interfaces.pump.DetailedBolusInfoStorage
|
||||||
|
import info.nightscout.interfaces.pump.BolusProgressData
|
||||||
|
import info.nightscout.interfaces.pump.PumpSync
|
||||||
|
import info.nightscout.interfaces.pump.defs.PumpType
|
||||||
|
import info.nightscout.interfaces.queue.Callback
|
||||||
|
import info.nightscout.interfaces.queue.CommandQueue
|
||||||
|
import info.nightscout.interfaces.ui.UiInteraction
|
||||||
|
import info.nightscout.pump.medtrum.MedtrumPlugin
|
||||||
|
import info.nightscout.pump.medtrum.MedtrumPump
|
||||||
|
import info.nightscout.pump.medtrum.R
|
||||||
|
import info.nightscout.pump.medtrum.code.ConnectionState
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.AlarmState
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
|
||||||
|
import info.nightscout.pump.medtrum.comm.packets.*
|
||||||
|
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
|
||||||
|
import info.nightscout.rx.AapsSchedulers
|
||||||
|
import info.nightscout.rx.bus.RxBus
|
||||||
|
import info.nightscout.rx.events.EventAppExit
|
||||||
|
import info.nightscout.rx.events.EventDismissNotification
|
||||||
|
import info.nightscout.rx.events.EventOverviewBolusProgress
|
||||||
|
import info.nightscout.rx.events.EventPreferenceChange
|
||||||
|
import info.nightscout.rx.events.EventPumpStatusChanged
|
||||||
|
import info.nightscout.rx.logging.AAPSLogger
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import info.nightscout.shared.interfaces.ResourceHelper
|
||||||
|
import info.nightscout.shared.sharedPreferences.SP
|
||||||
|
import info.nightscout.shared.utils.DateUtil
|
||||||
|
import info.nightscout.shared.utils.T
|
||||||
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
|
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.round
|
||||||
|
|
||||||
|
class MedtrumService : DaggerService(), BLECommCallback {
|
||||||
|
|
||||||
|
@Inject lateinit var injector: HasAndroidInjector
|
||||||
|
@Inject lateinit var aapsLogger: AAPSLogger
|
||||||
|
@Inject lateinit var aapsSchedulers: AapsSchedulers
|
||||||
|
@Inject lateinit var rxBus: RxBus
|
||||||
|
@Inject lateinit var sp: SP
|
||||||
|
@Inject lateinit var rh: ResourceHelper
|
||||||
|
@Inject lateinit var profileFunction: ProfileFunction
|
||||||
|
@Inject lateinit var commandQueue: CommandQueue
|
||||||
|
@Inject lateinit var context: Context
|
||||||
|
@Inject lateinit var medtrumPlugin: MedtrumPlugin
|
||||||
|
@Inject lateinit var medtrumPump: MedtrumPump
|
||||||
|
@Inject lateinit var activePlugin: ActivePlugin
|
||||||
|
@Inject lateinit var constraintChecker: Constraints
|
||||||
|
@Inject lateinit var uiInteraction: UiInteraction
|
||||||
|
@Inject lateinit var bleComm: BLEComm
|
||||||
|
@Inject lateinit var fabricPrivacy: FabricPrivacy
|
||||||
|
@Inject lateinit var pumpSync: PumpSync
|
||||||
|
@Inject lateinit var detailedBolusInfoStorage: DetailedBolusInfoStorage
|
||||||
|
@Inject lateinit var dateUtil: DateUtil
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val COMMAND_DEFAULT_TIMEOUT_SEC: Long = 60
|
||||||
|
private const val COMMAND_SYNC_TIMEOUT_SEC: Long = 120
|
||||||
|
private const val COMMAND_CONNECTING_TIMEOUT_SEC: Long = 30
|
||||||
|
private const val ALARM_HOURLY_MAX_CLEAR_CODE = 4
|
||||||
|
private const val ALARM_DAILY_MAX_CLEAR_CODE = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
val timeUtil = MedtrumTimeUtil()
|
||||||
|
|
||||||
|
private val disposable = CompositeDisposable()
|
||||||
|
private val mBinder: IBinder = LocalBinder()
|
||||||
|
|
||||||
|
private var currentState: State = IdleState()
|
||||||
|
private var mPacket: MedtrumPacket? = null
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Dispatchers.Default)
|
||||||
|
|
||||||
|
val isConnected: Boolean
|
||||||
|
get() = medtrumPump.connectionState == ConnectionState.CONNECTED
|
||||||
|
val isConnecting: Boolean
|
||||||
|
get() = medtrumPump.connectionState == ConnectionState.CONNECTING
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
bleComm.setCallback(this)
|
||||||
|
disposable += rxBus
|
||||||
|
.toObservable(EventAppExit::class.java)
|
||||||
|
.observeOn(aapsSchedulers.io)
|
||||||
|
.subscribe({ stopSelf() }, fabricPrivacy::logException)
|
||||||
|
disposable += rxBus
|
||||||
|
.toObservable(EventPreferenceChange::class.java)
|
||||||
|
.observeOn(aapsSchedulers.io)
|
||||||
|
.subscribe({ event ->
|
||||||
|
if (event.isChanged(rh.gs(R.string.key_sn_input))) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Serial number changed, reporting new pump!")
|
||||||
|
pumpSync.connectNewPump()
|
||||||
|
medtrumPump.loadUserSettingsFromSP()
|
||||||
|
medtrumPump.setFakeTBRIfNeeded()
|
||||||
|
}
|
||||||
|
if (event.isChanged(rh.gs(R.string.key_alarm_setting))
|
||||||
|
|| event.isChanged(rh.gs(R.string.key_patch_expiration))
|
||||||
|
|| event.isChanged(rh.gs(R.string.key_hourly_max_insulin))
|
||||||
|
|| event.isChanged(rh.gs(R.string.key_daily_max_insulin))
|
||||||
|
) {
|
||||||
|
medtrumPump.loadUserSettingsFromSP()
|
||||||
|
commandQueue.setUserOptions(object : Callback() {
|
||||||
|
override fun run() {
|
||||||
|
if (medtrumPlugin.isInitialized() && this.result.success == false) {
|
||||||
|
uiInteraction.addNotification(
|
||||||
|
Notification.PUMP_SETTINGS_FAILED,
|
||||||
|
rh.gs(R.string.pump_setting_failed),
|
||||||
|
Notification.NORMAL,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, fabricPrivacy::logException)
|
||||||
|
scope.launch {
|
||||||
|
medtrumPump.pumpStateFlow.collect { state ->
|
||||||
|
handlePumpStateUpdate(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scope.launch {
|
||||||
|
medtrumPump.connectionStateFlow.collect { state ->
|
||||||
|
if (medtrumPlugin.isInitialized()) {
|
||||||
|
when (state) {
|
||||||
|
ConnectionState.CONNECTED -> rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTED))
|
||||||
|
ConnectionState.DISCONNECTED -> rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED))
|
||||||
|
ConnectionState.CONNECTING -> rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTING))
|
||||||
|
ConnectionState.DISCONNECTING -> rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
medtrumPump.loadUserSettingsFromSP()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
disposable.clear()
|
||||||
|
scope.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun connect(from: String): Boolean {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "connect: called from: $from")
|
||||||
|
if (currentState is IdleState) {
|
||||||
|
medtrumPump.connectionState = ConnectionState.CONNECTING
|
||||||
|
return bleComm.connect(from, medtrumPump.pumpSN)
|
||||||
|
} else {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "Connect attempt when in non Idle state from: $from")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startPrime(): Boolean {
|
||||||
|
return sendPacketAndGetResponse(PrimePacket(injector))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startActivate(): Boolean {
|
||||||
|
val profile = profileFunction.getProfile()?.let { medtrumPump.buildMedtrumProfileArray(it) }
|
||||||
|
val packet = profile?.let { ActivatePacket(injector, it) }
|
||||||
|
return packet?.let { sendPacketAndGetResponse(it) } == true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deactivatePatch(): Boolean {
|
||||||
|
var result = true
|
||||||
|
if (medtrumPump.tempBasalInProgress) {
|
||||||
|
result = sendPacketAndGetResponse(CancelTempBasalPacket(injector))
|
||||||
|
}
|
||||||
|
// Make sure we have all events of this patch if possible
|
||||||
|
loadEvents()
|
||||||
|
if (result) result = sendPacketAndGetResponse(StopPatchPacket(injector))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopConnecting() {
|
||||||
|
bleComm.disconnect("stopConnecting")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun disconnect(from: String) {
|
||||||
|
medtrumPump.connectionState = ConnectionState.DISCONNECTING
|
||||||
|
bleComm.disconnect(from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readPumpStatus() {
|
||||||
|
rxBus.send(EventPumpStatusChanged(rh.gs(R.string.gettingpumpstatus)))
|
||||||
|
updateTimeIfNeeded(false)
|
||||||
|
loadEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun timeUpdateNotification(updateSuccess: Boolean) {
|
||||||
|
if (updateSuccess) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Pump time updated")
|
||||||
|
uiInteraction.addNotification(
|
||||||
|
Notification.INSIGHT_DATE_TIME_UPDATED, // :---)
|
||||||
|
rh.gs(info.nightscout.core.ui.R.string.pump_time_updated),
|
||||||
|
Notification.INFO,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "Failed to update pump time")
|
||||||
|
uiInteraction.addNotification(
|
||||||
|
Notification.PUMP_TIMEZONE_UPDATE_FAILED,
|
||||||
|
rh.gs(R.string.pump_time_update_failed),
|
||||||
|
Notification.URGENT,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateTimeIfNeeded(needLoadHistory: Boolean = true): Boolean {
|
||||||
|
// Note we only check timeZone here, time is updated each connection attempt if needed, because the pump requires it to be checked
|
||||||
|
// But we dont check timeZone each time, therefore we do it here (if needed)
|
||||||
|
var result = true
|
||||||
|
if (medtrumPump.pumpTimeZoneOffset != dateUtil.getTimeZoneOffsetMinutes(dateUtil.now())) {
|
||||||
|
result = sendPacketAndGetResponse(SetTimePacket(injector))
|
||||||
|
if (result) result = sendPacketAndGetResponse(SetTimeZonePacket(injector))
|
||||||
|
timeUpdateNotification(result)
|
||||||
|
}
|
||||||
|
// Do this here, because TBR can be cancelled due to time change by connect flow
|
||||||
|
if (needLoadHistory) {
|
||||||
|
if (result) result = loadEvents()
|
||||||
|
}
|
||||||
|
if (result) medtrumPump.needCheckTimeUpdate = false
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadEvents(): Boolean {
|
||||||
|
rxBus.send(EventPumpStatusChanged(rh.gs(R.string.gettingpumpstatus)))
|
||||||
|
// Sync records
|
||||||
|
val result = syncRecords()
|
||||||
|
if (result) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Events loaded")
|
||||||
|
medtrumPump.lastConnection = System.currentTimeMillis()
|
||||||
|
} else {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "Failed to load events")
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearAlarms(): Boolean {
|
||||||
|
var result = true
|
||||||
|
if (medtrumPump.pumpState in listOf(
|
||||||
|
MedtrumPumpState.PAUSED,
|
||||||
|
MedtrumPumpState.HMAX_SUSPENDED,
|
||||||
|
MedtrumPumpState.DMAX_SUSPENDED
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
when (medtrumPump.pumpState) {
|
||||||
|
MedtrumPumpState.HMAX_SUSPENDED -> {
|
||||||
|
result = sendPacketAndGetResponse(ClearPumpAlarmPacket(injector, ALARM_HOURLY_MAX_CLEAR_CODE))
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtrumPumpState.DMAX_SUSPENDED -> {
|
||||||
|
result = sendPacketAndGetResponse(ClearPumpAlarmPacket(injector, ALARM_DAILY_MAX_CLEAR_CODE))
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
// Nothing to reset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Resume suspended pump
|
||||||
|
if (result) result = sendPacketAndGetResponse(ResumePumpPacket(injector))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setUserSettings(): Boolean {
|
||||||
|
rxBus.send(EventPumpStatusChanged(rh.gs(R.string.settingpumpsettings)))
|
||||||
|
return sendPacketAndGetResponse(SetPatchPacket(injector))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setBolus(detailedBolusInfo: DetailedBolusInfo, t: EventOverviewBolusProgress.Treatment): Boolean {
|
||||||
|
if (!isConnected) return false
|
||||||
|
if (BolusProgressData.stopPressed) return false
|
||||||
|
val insulin = detailedBolusInfo.insulin
|
||||||
|
val bolusStart = System.currentTimeMillis()
|
||||||
|
|
||||||
|
medtrumPump.bolusDone = false
|
||||||
|
medtrumPump.bolusingTreatment = t
|
||||||
|
medtrumPump.bolusAmountToBeDelivered = insulin
|
||||||
|
medtrumPump.bolusStopped = false
|
||||||
|
medtrumPump.bolusProgressLastTimeStamp = bolusStart
|
||||||
|
|
||||||
|
if (insulin > 0 && !medtrumPump.bolusStopped) {
|
||||||
|
val result = sendPacketAndGetResponse(SetBolusPacket(injector, insulin))
|
||||||
|
if (result == false) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "Failed to set bolus")
|
||||||
|
commandQueue.loadEvents(null) // make sure if anything is delivered (which is highly unlikely at this point) we get it
|
||||||
|
t.insulin = 0.0
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Bolus not set, insulin: $insulin, bolusStopped: ${medtrumPump.bolusStopped}")
|
||||||
|
t.insulin = 0.0
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
detailedBolusInfo.timestamp = bolusStart // Make sure the timestamp is set to the start of the bolus
|
||||||
|
detailedBolusInfoStorage.add(detailedBolusInfo) // will be picked up on reading history
|
||||||
|
// Sync the initial bolus
|
||||||
|
val newRecord = pumpSync.addBolusWithTempId(
|
||||||
|
timestamp = detailedBolusInfo.timestamp,
|
||||||
|
amount = detailedBolusInfo.insulin,
|
||||||
|
temporaryId = detailedBolusInfo.timestamp,
|
||||||
|
type = detailedBolusInfo.bolusType,
|
||||||
|
pumpType = medtrumPump.pumpType(),
|
||||||
|
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
|
||||||
|
)
|
||||||
|
if (newRecord) {
|
||||||
|
aapsLogger.debug(
|
||||||
|
LTag.PUMPCOMM,
|
||||||
|
"set bolus: **NEW** EVENT BOLUS (tempId) ${dateUtil.dateAndTimeString(detailedBolusInfo.timestamp)} (${detailedBolusInfo.timestamp}) Bolus: ${detailedBolusInfo.insulin}U "
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "Bolus with tempId ${detailedBolusInfo.timestamp} already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
val bolusingEvent = EventOverviewBolusProgress
|
||||||
|
var communicationLost = false
|
||||||
|
|
||||||
|
while (!medtrumPump.bolusStopped && !medtrumPump.bolusDone && !communicationLost) {
|
||||||
|
SystemClock.sleep(100)
|
||||||
|
if (System.currentTimeMillis() - medtrumPump.bolusProgressLastTimeStamp > T.secs(20).msecs()) {
|
||||||
|
communicationLost = true
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "Communication stopped")
|
||||||
|
disconnect("Communication stopped")
|
||||||
|
} else {
|
||||||
|
bolusingEvent.t = medtrumPump.bolusingTreatment
|
||||||
|
bolusingEvent.status = rh.gs(info.nightscout.pump.common.R.string.bolus_delivered_so_far, medtrumPump.bolusingTreatment?.insulin, medtrumPump.bolusAmountToBeDelivered)
|
||||||
|
bolusingEvent.percent = round((medtrumPump.bolusingTreatment?.insulin?.div(medtrumPump.bolusAmountToBeDelivered) ?: 0.0) * 100).toInt() - 1
|
||||||
|
rxBus.send(bolusingEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bolusingEvent.percent = 99
|
||||||
|
val bolusDurationInMSec = (insulin * 60 * 1000)
|
||||||
|
val expectedEnd = bolusStart + bolusDurationInMSec + 2000
|
||||||
|
while (System.currentTimeMillis() < expectedEnd && !medtrumPump.bolusDone) {
|
||||||
|
SystemClock.sleep(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
bolusingEvent.t = medtrumPump.bolusingTreatment
|
||||||
|
medtrumPump.bolusingTreatment = null
|
||||||
|
|
||||||
|
if (medtrumPump.bolusStopped && t.insulin == 0.0) {
|
||||||
|
// In this case we don't get a bolus end event, so need to remove all the stuff added previously
|
||||||
|
val syncOk = pumpSync.syncBolusWithTempId(
|
||||||
|
timestamp = bolusStart,
|
||||||
|
amount = 0.0,
|
||||||
|
temporaryId = bolusStart,
|
||||||
|
type = detailedBolusInfo.bolusType,
|
||||||
|
pumpId = bolusStart,
|
||||||
|
pumpType = medtrumPump.pumpType(),
|
||||||
|
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
|
||||||
|
)
|
||||||
|
aapsLogger.debug(
|
||||||
|
LTag.PUMPCOMM,
|
||||||
|
"set bolus: **SYNC** EVENT BOLUS (tempId) ${dateUtil.dateAndTimeString(detailedBolusInfo.timestamp)} (${bolusStart}) Bolus: ${0.0}U SyncOK: $syncOk"
|
||||||
|
)
|
||||||
|
// remove detailed bolus info
|
||||||
|
detailedBolusInfoStorage.findDetailedBolusInfo(bolusStart, detailedBolusInfo.insulin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not call update status directly, reconnection may be needed
|
||||||
|
commandQueue.loadEvents(object : Callback() {
|
||||||
|
override fun run() {
|
||||||
|
rxBus.send(EventPumpStatusChanged(rh.gs(R.string.gettingbolusstatus)))
|
||||||
|
bolusingEvent.percent = 100
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopBolus() {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "bolusStop >>>>> @ " + if (medtrumPump.bolusingTreatment == null) "" else medtrumPump.bolusingTreatment?.insulin)
|
||||||
|
if (isConnected) {
|
||||||
|
var success = sendPacketAndGetResponse(CancelBolusPacket(injector))
|
||||||
|
var timeout = System.currentTimeMillis() + T.secs(30).msecs()
|
||||||
|
while (!success && System.currentTimeMillis() < timeout) {
|
||||||
|
success = sendPacketAndGetResponse(CancelBolusPacket(injector))
|
||||||
|
SystemClock.sleep(200)
|
||||||
|
}
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "bolusStop success: $success")
|
||||||
|
medtrumPump.bolusStopped = true
|
||||||
|
} else {
|
||||||
|
medtrumPump.bolusStopped = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTempBasal(absoluteRate: Double, durationInMinutes: Int): Boolean {
|
||||||
|
var result = true
|
||||||
|
if (medtrumPump.tempBasalInProgress) {
|
||||||
|
result = sendPacketAndGetResponse(CancelTempBasalPacket(injector))
|
||||||
|
}
|
||||||
|
if (result) result = sendPacketAndGetResponse(SetTempBasalPacket(injector, absoluteRate, durationInMinutes))
|
||||||
|
|
||||||
|
// Get history records, this will update the prevoius basals
|
||||||
|
// Do not call update status directly, reconnection may be needed
|
||||||
|
commandQueue.loadEvents(object : Callback() {
|
||||||
|
override fun run() {
|
||||||
|
rxBus.send(EventPumpStatusChanged(rh.gs(R.string.gettingtempbasalstatus)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelTempBasal(): Boolean {
|
||||||
|
var result = sendPacketAndGetResponse(CancelTempBasalPacket(injector))
|
||||||
|
|
||||||
|
// Get history records, this will update the prevoius basals
|
||||||
|
// Do not call update status directly, reconnection may be needed
|
||||||
|
commandQueue.loadEvents(object : Callback() {
|
||||||
|
override fun run() {
|
||||||
|
rxBus.send(EventPumpStatusChanged(rh.gs(R.string.gettingtempbasalstatus)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateBasalsInPump(profile: Profile): Boolean {
|
||||||
|
var result = true
|
||||||
|
// Update basal affects the TBR records (the pump will cancel the TBR, set our basal profile, and resume the TBR in a new record)
|
||||||
|
// Cancel any TBR in progress
|
||||||
|
if (medtrumPump.tempBasalInProgress) {
|
||||||
|
result = sendPacketAndGetResponse(CancelTempBasalPacket(injector))
|
||||||
|
}
|
||||||
|
val packet = medtrumPump.buildMedtrumProfileArray(profile)?.let { SetBasalProfilePacket(injector, it) }
|
||||||
|
if (result) result = packet?.let { sendPacketAndGetResponse(it) } == true
|
||||||
|
|
||||||
|
// Get history records, this will update the pump state and add changes in TBR to AAPS history
|
||||||
|
commandQueue.loadEvents(null)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This gets the history records from the pump */
|
||||||
|
private fun syncRecords(): Boolean {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "syncRecords: called!, syncedSequenceNumber: ${medtrumPump.syncedSequenceNumber}, currentSequenceNumber: ${medtrumPump.currentSequenceNumber}")
|
||||||
|
var result = true
|
||||||
|
// Note: medtrum app fetches all records when they sync?
|
||||||
|
if (medtrumPump.syncedSequenceNumber < medtrumPump.currentSequenceNumber) {
|
||||||
|
for (sequence in (medtrumPump.syncedSequenceNumber + 1)..medtrumPump.currentSequenceNumber) {
|
||||||
|
result = sendPacketAndGetResponse(GetRecordPacket(injector, sequence), COMMAND_SYNC_TIMEOUT_SEC)
|
||||||
|
SystemClock.sleep(100)
|
||||||
|
if (result == false) break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handlePumpStateUpdate(state: MedtrumPumpState) {
|
||||||
|
// Map the pump state to an alarm state and add it to the active alarms
|
||||||
|
val alarmState = when (state) {
|
||||||
|
MedtrumPumpState.NONE -> AlarmState.NONE
|
||||||
|
MedtrumPumpState.LOWBG_SUSPENDED -> AlarmState.LOWBG_SUSPENDED
|
||||||
|
MedtrumPumpState.LOWBG_SUSPENDED2 -> AlarmState.LOWBG_SUSPENDED2
|
||||||
|
MedtrumPumpState.AUTO_SUSPENDED -> AlarmState.AUTO_SUSPENDED
|
||||||
|
MedtrumPumpState.HMAX_SUSPENDED -> AlarmState.HMAX_SUSPENDED
|
||||||
|
MedtrumPumpState.DMAX_SUSPENDED -> AlarmState.DMAX_SUSPENDED
|
||||||
|
MedtrumPumpState.SUSPENDED -> AlarmState.SUSPENDED
|
||||||
|
MedtrumPumpState.PAUSED -> AlarmState.PAUSED
|
||||||
|
MedtrumPumpState.OCCLUSION -> AlarmState.OCCLUSION
|
||||||
|
MedtrumPumpState.EXPIRED -> AlarmState.EXPIRED
|
||||||
|
MedtrumPumpState.RESERVOIR_EMPTY -> AlarmState.RESERVOIR_EMPTY
|
||||||
|
MedtrumPumpState.PATCH_FAULT -> AlarmState.PATCH_FAULT
|
||||||
|
MedtrumPumpState.PATCH_FAULT2 -> AlarmState.PATCH_FAULT2
|
||||||
|
MedtrumPumpState.BASE_FAULT -> AlarmState.BASE_FAULT
|
||||||
|
MedtrumPumpState.BATTERY_OUT -> AlarmState.BATTERY_OUT
|
||||||
|
MedtrumPumpState.NO_CALIBRATION -> AlarmState.NO_CALIBRATION
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
if (alarmState != null && alarmState != AlarmState.NONE) {
|
||||||
|
medtrumPump.addAlarm(alarmState)
|
||||||
|
pumpSync.insertAnnouncement(
|
||||||
|
medtrumPump.alarmStateToString(alarmState),
|
||||||
|
null,
|
||||||
|
medtrumPump.pumpType(),
|
||||||
|
medtrumPump.pumpSN.toString(radix = 16)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map the pump state to a notification
|
||||||
|
when (state) {
|
||||||
|
MedtrumPumpState.NONE,
|
||||||
|
MedtrumPumpState.STOPPED -> {
|
||||||
|
rxBus.send(EventDismissNotification(Notification.PUMP_ERROR))
|
||||||
|
rxBus.send(EventDismissNotification(Notification.PUMP_SUSPENDED))
|
||||||
|
uiInteraction.addNotification(
|
||||||
|
Notification.PATCH_NOT_ACTIVE,
|
||||||
|
rh.gs(R.string.patch_not_active),
|
||||||
|
Notification.URGENT,
|
||||||
|
)
|
||||||
|
medtrumPump.setFakeTBRIfNeeded()
|
||||||
|
medtrumPump.clearAlarmState()
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtrumPumpState.IDLE,
|
||||||
|
MedtrumPumpState.FILLED,
|
||||||
|
MedtrumPumpState.PRIMING,
|
||||||
|
MedtrumPumpState.PRIMED,
|
||||||
|
MedtrumPumpState.EJECTING,
|
||||||
|
MedtrumPumpState.EJECTED -> {
|
||||||
|
rxBus.send(EventDismissNotification(Notification.PUMP_ERROR))
|
||||||
|
rxBus.send(EventDismissNotification(Notification.PUMP_SUSPENDED))
|
||||||
|
medtrumPump.setFakeTBRIfNeeded()
|
||||||
|
medtrumPump.clearAlarmState()
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtrumPumpState.ACTIVE,
|
||||||
|
MedtrumPumpState.ACTIVE_ALT -> {
|
||||||
|
rxBus.send(EventDismissNotification(Notification.PATCH_NOT_ACTIVE))
|
||||||
|
rxBus.send(EventDismissNotification(Notification.PUMP_SUSPENDED))
|
||||||
|
medtrumPump.clearAlarmState()
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtrumPumpState.LOWBG_SUSPENDED,
|
||||||
|
MedtrumPumpState.LOWBG_SUSPENDED2,
|
||||||
|
MedtrumPumpState.AUTO_SUSPENDED,
|
||||||
|
MedtrumPumpState.SUSPENDED,
|
||||||
|
MedtrumPumpState.PAUSED -> {
|
||||||
|
uiInteraction.addNotification(
|
||||||
|
Notification.PUMP_SUSPENDED,
|
||||||
|
rh.gs(R.string.pump_is_suspended),
|
||||||
|
Notification.NORMAL,
|
||||||
|
)
|
||||||
|
// Pump will report proper TBR for this
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtrumPumpState.HMAX_SUSPENDED -> {
|
||||||
|
uiInteraction.addNotification(
|
||||||
|
Notification.PUMP_SUSPENDED,
|
||||||
|
rh.gs(R.string.pump_is_suspended_hour_max),
|
||||||
|
Notification.NORMAL,
|
||||||
|
)
|
||||||
|
// Pump will report proper TBR for this
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtrumPumpState.DMAX_SUSPENDED -> {
|
||||||
|
uiInteraction.addNotification(
|
||||||
|
Notification.PUMP_SUSPENDED,
|
||||||
|
rh.gs(R.string.pump_is_suspended_day_max),
|
||||||
|
Notification.NORMAL,
|
||||||
|
)
|
||||||
|
// Pump will report proper TBR for this
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtrumPumpState.OCCLUSION,
|
||||||
|
MedtrumPumpState.EXPIRED,
|
||||||
|
MedtrumPumpState.RESERVOIR_EMPTY,
|
||||||
|
MedtrumPumpState.PATCH_FAULT,
|
||||||
|
MedtrumPumpState.PATCH_FAULT2,
|
||||||
|
MedtrumPumpState.BASE_FAULT,
|
||||||
|
MedtrumPumpState.BATTERY_OUT,
|
||||||
|
MedtrumPumpState.NO_CALIBRATION -> {
|
||||||
|
rxBus.send(EventDismissNotification(Notification.PATCH_NOT_ACTIVE))
|
||||||
|
rxBus.send(EventDismissNotification(Notification.PUMP_SUSPENDED))
|
||||||
|
// Pump suspended due to error, show error!
|
||||||
|
uiInteraction.addNotificationWithSound(
|
||||||
|
Notification.PUMP_ERROR,
|
||||||
|
rh.gs(R.string.pump_error, alarmState?.let { medtrumPump.alarmStateToString(it) }),
|
||||||
|
Notification.URGENT,
|
||||||
|
info.nightscout.core.ui.R.raw.alarm
|
||||||
|
)
|
||||||
|
medtrumPump.setFakeTBRIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** BLECommCallbacks */
|
||||||
|
override fun onBLEConnected() {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "<<<<< onBLEConnected")
|
||||||
|
currentState.onConnected()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBLEDisconnected() {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "<<<<< onBLEDisconnected")
|
||||||
|
currentState.onDisconnected()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNotification(notification: ByteArray) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "<<<<< onNotification" + notification.contentToString())
|
||||||
|
NotificationPacket(injector).handleNotification(notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onIndication(indication: ByteArray) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "<<<<< onIndication" + indication.contentToString())
|
||||||
|
currentState.onIndication(indication)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSendMessageError(reason: String) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "<<<<< error during send message $reason")
|
||||||
|
currentState.onSendMessageError(reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Service stuff */
|
||||||
|
inner class LocalBinder : Binder() {
|
||||||
|
|
||||||
|
val serviceInstance: MedtrumService
|
||||||
|
get() = this@MedtrumService
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent): IBinder {
|
||||||
|
return mBinder
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||||
|
return Service.START_STICKY
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* States are used to keep track of the communication and to guide the flow
|
||||||
|
*/
|
||||||
|
private fun toState(nextState: State) {
|
||||||
|
currentState = nextState
|
||||||
|
currentState.onEnter()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendPacketAndGetResponse(packet: MedtrumPacket, timeout: Long = COMMAND_DEFAULT_TIMEOUT_SEC): Boolean {
|
||||||
|
var result = false
|
||||||
|
if (currentState is ReadyState) {
|
||||||
|
toState(CommandState())
|
||||||
|
mPacket = packet
|
||||||
|
mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
|
||||||
|
result = currentState.waitForResponse(timeout)
|
||||||
|
} else {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "Send packet attempt when in non Ready state")
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// State class
|
||||||
|
private abstract inner class State {
|
||||||
|
|
||||||
|
protected var responseHandled = false
|
||||||
|
protected var responseSuccess = false
|
||||||
|
protected var sendRetryCounter = 0
|
||||||
|
|
||||||
|
open fun onEnter() {}
|
||||||
|
open fun onIndication(data: ByteArray) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "onIndication: " + this.toString() + "Should not be called here!")
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun onConnected() {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "onConnected")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDisconnected() {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "onDisconnected")
|
||||||
|
medtrumPump.connectionState = ConnectionState.DISCONNECTED
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = false
|
||||||
|
toState(IdleState())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun waitForResponse(timeout: Long): Boolean {
|
||||||
|
val startTime = System.currentTimeMillis()
|
||||||
|
val timeoutMillis = T.secs(timeout).msecs()
|
||||||
|
while (!responseHandled) {
|
||||||
|
if (System.currentTimeMillis() - startTime > timeoutMillis) {
|
||||||
|
// If we haven't received a response in the specified time, assume the command failed
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service State timeout")
|
||||||
|
// Disconnect to cancel any outstanding commands and go back to ready state
|
||||||
|
disconnect("Timeout")
|
||||||
|
toState(IdleState())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
SystemClock.sleep(25)
|
||||||
|
}
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service State responseHandled: $responseHandled responseSuccess: $responseSuccess")
|
||||||
|
return responseSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onSendMessageError(reason: String) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "onSendMessageError: " + this.toString() + "reason: $reason")
|
||||||
|
// Retry 3 times
|
||||||
|
if (sendRetryCounter < 3) {
|
||||||
|
sendRetryCounter++
|
||||||
|
mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
|
||||||
|
} else {
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = false
|
||||||
|
disconnect("onSendMessageError")
|
||||||
|
toState(IdleState())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class IdleState : State() {
|
||||||
|
|
||||||
|
override fun onEnter() {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached IdleState")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConnected() {
|
||||||
|
super.onConnected()
|
||||||
|
toState(AuthState())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// State for connect flow
|
||||||
|
private inner class AuthState : State() {
|
||||||
|
|
||||||
|
val retryCounter = 0
|
||||||
|
|
||||||
|
override fun onEnter() {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached AuthState")
|
||||||
|
mPacket = AuthorizePacket(injector)
|
||||||
|
mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
|
||||||
|
scope.launch {
|
||||||
|
waitForResponse(COMMAND_CONNECTING_TIMEOUT_SEC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onIndication(data: ByteArray) {
|
||||||
|
if (mPacket?.handleResponse(data) == true) {
|
||||||
|
// Succes!
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = true
|
||||||
|
// Check if we have a supported pump
|
||||||
|
if (medtrumPump.pumpType() == PumpType.MEDTRUM_UNTESTED) {
|
||||||
|
// Throw error
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "Unsupported pump type")
|
||||||
|
uiInteraction.addNotificationWithSound(
|
||||||
|
Notification.PUMP_ERROR,
|
||||||
|
rh.gs(R.string.pump_unsupported, medtrumPump.deviceType),
|
||||||
|
Notification.URGENT,
|
||||||
|
info.nightscout.core.ui.R.raw.alarm
|
||||||
|
)
|
||||||
|
disconnect("Unsupported pump")
|
||||||
|
toState(IdleState())
|
||||||
|
} else {
|
||||||
|
toState(GetDeviceTypeState())
|
||||||
|
}
|
||||||
|
} else if (mPacket?.failed == true) {
|
||||||
|
// Failure
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = false
|
||||||
|
disconnect("Failure")
|
||||||
|
toState(IdleState())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// State for connect flow
|
||||||
|
private inner class GetDeviceTypeState : State() {
|
||||||
|
|
||||||
|
override fun onEnter() {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached GetDeviceTypeState")
|
||||||
|
mPacket = GetDeviceTypePacket(injector)
|
||||||
|
mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
|
||||||
|
scope.launch {
|
||||||
|
waitForResponse(COMMAND_CONNECTING_TIMEOUT_SEC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onIndication(data: ByteArray) {
|
||||||
|
if (mPacket?.handleResponse(data) == true) {
|
||||||
|
// Succes!
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = true
|
||||||
|
// Place holder, not really used (yet)
|
||||||
|
val deviceType = (mPacket as GetDeviceTypePacket).deviceType
|
||||||
|
val deviceSN = (mPacket as GetDeviceTypePacket).deviceSN
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "GetDeviceTypeState: deviceType: $deviceType deviceSN: $deviceSN")
|
||||||
|
toState(GetTimeState())
|
||||||
|
} else if (mPacket?.failed == true) {
|
||||||
|
// Failure
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = false
|
||||||
|
disconnect("Failure")
|
||||||
|
toState(IdleState())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// State for connect flow
|
||||||
|
private inner class GetTimeState : State() {
|
||||||
|
|
||||||
|
override fun onEnter() {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached GetTimeState")
|
||||||
|
mPacket = GetTimePacket(injector)
|
||||||
|
mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
|
||||||
|
scope.launch {
|
||||||
|
waitForResponse(COMMAND_CONNECTING_TIMEOUT_SEC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onIndication(data: ByteArray) {
|
||||||
|
if (mPacket?.handleResponse(data) == true) {
|
||||||
|
// Succes!
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = true
|
||||||
|
val currTime = dateUtil.now()
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "GetTimeState.onIndication systemTime: $currTime, pumpTime: ${medtrumPump.lastTimeReceivedFromPump}")
|
||||||
|
if (abs(medtrumPump.lastTimeReceivedFromPump - currTime) <= T.secs(10).msecs()) { // Allow 10 sec deviation
|
||||||
|
toState(SynchronizeState())
|
||||||
|
} else {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "GetTimeState.onIndication time difference too big, setting time")
|
||||||
|
toState(SetTimeState())
|
||||||
|
}
|
||||||
|
} else if (mPacket?.failed == true) {
|
||||||
|
// Failure
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = false
|
||||||
|
disconnect("Failure")
|
||||||
|
toState(IdleState())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// State for connect flow
|
||||||
|
private inner class SetTimeState : State() {
|
||||||
|
|
||||||
|
override fun onEnter() {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached SetTimeState")
|
||||||
|
mPacket = SetTimePacket(injector)
|
||||||
|
mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
|
||||||
|
scope.launch {
|
||||||
|
waitForResponse(COMMAND_CONNECTING_TIMEOUT_SEC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onIndication(data: ByteArray) {
|
||||||
|
if (mPacket?.handleResponse(data) == true) {
|
||||||
|
// Succes!
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = true
|
||||||
|
toState(SetTimeZoneState())
|
||||||
|
} else if (mPacket?.failed == true) {
|
||||||
|
// Failure
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = false
|
||||||
|
disconnect("Failure")
|
||||||
|
toState(IdleState())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// State for connect flow
|
||||||
|
private inner class SetTimeZoneState : State() {
|
||||||
|
|
||||||
|
override fun onEnter() {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached SetTimeZoneState")
|
||||||
|
mPacket = SetTimeZonePacket(injector)
|
||||||
|
mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
|
||||||
|
scope.launch {
|
||||||
|
waitForResponse(COMMAND_CONNECTING_TIMEOUT_SEC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onIndication(data: ByteArray) {
|
||||||
|
if (mPacket?.handleResponse(data) == true) {
|
||||||
|
// Succes!
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = true
|
||||||
|
medtrumPump.needCheckTimeUpdate = false
|
||||||
|
timeUpdateNotification(true)
|
||||||
|
toState(SynchronizeState())
|
||||||
|
} else if (mPacket?.failed == true) {
|
||||||
|
// Failure
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = false
|
||||||
|
disconnect("Failure")
|
||||||
|
toState(IdleState())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// State for connect flow
|
||||||
|
private inner class SynchronizeState : State() {
|
||||||
|
|
||||||
|
override fun onEnter() {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached SynchronizeState")
|
||||||
|
mPacket = SynchronizePacket(injector)
|
||||||
|
mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
|
||||||
|
scope.launch {
|
||||||
|
waitForResponse(COMMAND_CONNECTING_TIMEOUT_SEC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onIndication(data: ByteArray) {
|
||||||
|
if (mPacket?.handleResponse(data) == true) {
|
||||||
|
// Succes!
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = true
|
||||||
|
toState(SubscribeState())
|
||||||
|
} else if (mPacket?.failed == true) {
|
||||||
|
// Failure
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = false
|
||||||
|
disconnect("Failure")
|
||||||
|
toState(IdleState())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// State for connect flow
|
||||||
|
private inner class SubscribeState : State() {
|
||||||
|
|
||||||
|
override fun onEnter() {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached SubscribeState")
|
||||||
|
mPacket = SubscribePacket(injector)
|
||||||
|
mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
|
||||||
|
scope.launch {
|
||||||
|
waitForResponse(COMMAND_CONNECTING_TIMEOUT_SEC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onIndication(data: ByteArray) {
|
||||||
|
if (mPacket?.handleResponse(data) == true) {
|
||||||
|
// Succes!
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = true
|
||||||
|
toState(ReadyState())
|
||||||
|
} else if (mPacket?.failed == true) {
|
||||||
|
// Failure
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = false
|
||||||
|
disconnect("Failure")
|
||||||
|
toState(IdleState())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This state is reached when the patch is ready to receive commands (Activation, Bolus, temp basal and whatever)
|
||||||
|
private inner class ReadyState : State() {
|
||||||
|
|
||||||
|
override fun onEnter() {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached ReadyState!")
|
||||||
|
// Now we are fully connected and authenticated and we can start sending commands. Let AAPS know
|
||||||
|
if (isConnected == false) {
|
||||||
|
medtrumPump.connectionState = ConnectionState.CONNECTED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This state is when a command is send and we wait for a response for that command
|
||||||
|
private inner class CommandState : State() {
|
||||||
|
|
||||||
|
override fun onEnter() {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached CommandState")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onIndication(data: ByteArray) {
|
||||||
|
if (mPacket?.handleResponse(data) == true) {
|
||||||
|
// Succes!
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = true
|
||||||
|
toState(ReadyState())
|
||||||
|
} else if (mPacket?.failed == true) {
|
||||||
|
// Failure
|
||||||
|
responseHandled = true
|
||||||
|
responseSuccess = false
|
||||||
|
toState(ReadyState())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package info.nightscout.pump.medtrum.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import info.nightscout.core.ui.toast.ToastUtils
|
||||||
|
import info.nightscout.pump.medtrum.R
|
||||||
|
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumActivateCompleteBinding
|
||||||
|
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
|
||||||
|
import info.nightscout.rx.logging.AAPSLogger
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import info.nightscout.shared.interfaces.ResourceHelper
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class MedtrumActivateCompleteFragment : MedtrumBaseFragment<FragmentMedtrumActivateCompleteBinding>() {
|
||||||
|
|
||||||
|
@Inject lateinit var aapsLogger: AAPSLogger
|
||||||
|
@Inject lateinit var rh: ResourceHelper
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newInstance(): MedtrumActivateCompleteFragment = MedtrumActivateCompleteFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLayoutId(): Int = R.layout.fragment_medtrum_activate_complete
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
binding.apply {
|
||||||
|
viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
|
||||||
|
viewModel?.apply {
|
||||||
|
setupStep.observe(viewLifecycleOwner) {
|
||||||
|
when (it) {
|
||||||
|
MedtrumViewModel.SetupStep.INITIAL,
|
||||||
|
MedtrumViewModel.SetupStep.PRIMED -> Unit // Nothing to do here, previous state
|
||||||
|
MedtrumViewModel.SetupStep.ACTIVATED -> btnPositive.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
ToastUtils.errorToast(requireContext(), rh.gs(R.string.unexpected_state, it.toString()))
|
||||||
|
aapsLogger.error(LTag.PUMP, "Unexpected state: $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package info.nightscout.pump.medtrum.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import info.nightscout.core.ui.dialogs.OKDialog
|
||||||
|
import info.nightscout.core.ui.toast.ToastUtils
|
||||||
|
import info.nightscout.pump.medtrum.R
|
||||||
|
import info.nightscout.pump.medtrum.code.PatchStep
|
||||||
|
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumActivateBinding
|
||||||
|
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
|
||||||
|
import info.nightscout.rx.logging.AAPSLogger
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import info.nightscout.shared.interfaces.ResourceHelper
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class MedtrumActivateFragment : MedtrumBaseFragment<FragmentMedtrumActivateBinding>() {
|
||||||
|
|
||||||
|
@Inject lateinit var aapsLogger: AAPSLogger
|
||||||
|
@Inject lateinit var rh: ResourceHelper
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newInstance(): MedtrumActivateFragment = MedtrumActivateFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLayoutId(): Int = R.layout.fragment_medtrum_activate
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
binding.apply {
|
||||||
|
viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
|
||||||
|
viewModel?.apply {
|
||||||
|
setupStep.observe(viewLifecycleOwner) {
|
||||||
|
when (it) {
|
||||||
|
MedtrumViewModel.SetupStep.INITIAL,
|
||||||
|
MedtrumViewModel.SetupStep.PRIMED -> Unit // Nothing to do here, previous state
|
||||||
|
MedtrumViewModel.SetupStep.ACTIVATED -> moveStep(PatchStep.ACTIVATE_COMPLETE)
|
||||||
|
|
||||||
|
MedtrumViewModel.SetupStep.ERROR -> {
|
||||||
|
moveStep(PatchStep.ERROR)
|
||||||
|
updateSetupStep(MedtrumViewModel.SetupStep.PRIMED) // Reset setup step
|
||||||
|
binding.textActivatingPump.text = rh.gs(R.string.activating_error)
|
||||||
|
binding.btnPositive.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
ToastUtils.errorToast(requireContext(), "Unexpected state: $it")
|
||||||
|
aapsLogger.error(LTag.PUMP, "Unexpected state: $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startActivate()
|
||||||
|
}
|
||||||
|
btnNegative.setOnClickListener {
|
||||||
|
OKDialog.showConfirmation(requireActivity(), rh.gs(R.string.cancel_sure)) {
|
||||||
|
viewModel?.apply {
|
||||||
|
moveStep(PatchStep.CANCEL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
package info.nightscout.pump.medtrum.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.WindowManager
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import info.nightscout.core.utils.extensions.safeGetSerializableExtra
|
||||||
|
import info.nightscout.pump.medtrum.R
|
||||||
|
import info.nightscout.pump.medtrum.code.PatchStep
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
|
||||||
|
import info.nightscout.pump.medtrum.databinding.ActivityMedtrumBinding
|
||||||
|
import info.nightscout.pump.medtrum.extension.replaceFragmentInActivity
|
||||||
|
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
|
||||||
|
|
||||||
|
class MedtrumActivity : MedtrumBaseActivity<ActivityMedtrumBinding>() {
|
||||||
|
|
||||||
|
override fun getLayoutId(): Int = R.layout.activity_medtrum
|
||||||
|
|
||||||
|
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
||||||
|
return super.dispatchTouchEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
|
||||||
|
binding.apply {
|
||||||
|
viewModel = ViewModelProvider(this@MedtrumActivity, viewModelFactory).get(MedtrumViewModel::class.java)
|
||||||
|
viewModel?.apply {
|
||||||
|
processIntent(intent)
|
||||||
|
|
||||||
|
patchStep.observe(this@MedtrumActivity) {
|
||||||
|
when (it) {
|
||||||
|
PatchStep.PREPARE_PATCH -> setupViewFragment(MedtrumPreparePatchFragment.newInstance())
|
||||||
|
PatchStep.PREPARE_PATCH_CONNECT -> setupViewFragment(MedtrumPreparePatchConnectFragment.newInstance())
|
||||||
|
PatchStep.PRIME -> setupViewFragment(MedtrumPrimeFragment.newInstance())
|
||||||
|
PatchStep.PRIMING -> setupViewFragment(MedtrumPrimingFragment.newInstance())
|
||||||
|
PatchStep.PRIME_COMPLETE -> setupViewFragment(MedtrumPrimeCompleteFragment.newInstance())
|
||||||
|
PatchStep.ATTACH_PATCH -> setupViewFragment(MedtrumAttachPatchFragment.newInstance())
|
||||||
|
PatchStep.ACTIVATE -> setupViewFragment(MedtrumActivateFragment.newInstance())
|
||||||
|
PatchStep.ACTIVATE_COMPLETE -> setupViewFragment(MedtrumActivateCompleteFragment.newInstance())
|
||||||
|
PatchStep.CANCEL,
|
||||||
|
PatchStep.COMPLETE -> this@MedtrumActivity.finish()
|
||||||
|
PatchStep.ERROR -> Unit // Do nothing, let activity handle this
|
||||||
|
PatchStep.RETRY_ACTIVATION -> setupViewFragment(MedtrumRetryActivationFragment.newInstance())
|
||||||
|
PatchStep.RETRY_ACTIVATION_CONNECT -> setupViewFragment(MedtrumRetryActivationConnectFragment.newInstance())
|
||||||
|
PatchStep.START_DEACTIVATION -> setupViewFragment(MedtrumStartDeactivationFragment.newInstance())
|
||||||
|
PatchStep.DEACTIVATE -> setupViewFragment(MedtrumDeactivatePatchFragment.newInstance())
|
||||||
|
|
||||||
|
PatchStep.FORCE_DEACTIVATION -> {
|
||||||
|
medtrumPump.pumpState = MedtrumPumpState.STOPPED
|
||||||
|
moveStep(PatchStep.DEACTIVATION_COMPLETE)
|
||||||
|
}
|
||||||
|
|
||||||
|
PatchStep.DEACTIVATION_COMPLETE -> setupViewFragment(MedtrumDeactivationCompleteFragment.newInstance())
|
||||||
|
null -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent?) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
processIntent(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processIntent(intent: Intent?) {
|
||||||
|
binding.viewModel?.apply {
|
||||||
|
intent?.run {
|
||||||
|
val step = intent.safeGetSerializableExtra(EXTRA_START_PATCH_STEP, PatchStep::class.java)
|
||||||
|
if (step != null) {
|
||||||
|
initializePatchStep(step)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
binding.viewModel?.apply {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val EXTRA_START_PATCH_STEP = "EXTRA_START_PATCH_FRAGMENT_UI"
|
||||||
|
const val EXTRA_START_FROM_MENU = "EXTRA_START_FROM_MENU"
|
||||||
|
|
||||||
|
@JvmStatic fun createIntentFromMenu(context: Context, patchStep: PatchStep): Intent {
|
||||||
|
return Intent(context, MedtrumActivity::class.java).apply {
|
||||||
|
putExtra(EXTRA_START_PATCH_STEP, patchStep)
|
||||||
|
putExtra(EXTRA_START_FROM_MENU, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupViewFragment(baseFragment: MedtrumBaseFragment<*>) {
|
||||||
|
replaceFragmentInActivity(baseFragment, R.id.framelayout_fragment, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package info.nightscout.pump.medtrum.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import info.nightscout.core.ui.dialogs.OKDialog
|
||||||
|
import info.nightscout.core.ui.toast.ToastUtils
|
||||||
|
import info.nightscout.pump.medtrum.R
|
||||||
|
import info.nightscout.pump.medtrum.code.PatchStep
|
||||||
|
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumAttachPatchBinding
|
||||||
|
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
|
||||||
|
import info.nightscout.rx.logging.AAPSLogger
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import info.nightscout.shared.interfaces.ResourceHelper
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class MedtrumAttachPatchFragment : MedtrumBaseFragment<FragmentMedtrumAttachPatchBinding>() {
|
||||||
|
|
||||||
|
@Inject lateinit var aapsLogger: AAPSLogger
|
||||||
|
@Inject lateinit var rh: ResourceHelper
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newInstance(): MedtrumAttachPatchFragment = MedtrumAttachPatchFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLayoutId(): Int = R.layout.fragment_medtrum_attach_patch
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
aapsLogger.debug(LTag.PUMP, "MedtrumAttachPatchFragment onViewCreated")
|
||||||
|
binding.apply {
|
||||||
|
viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
|
||||||
|
viewModel?.apply {
|
||||||
|
setupStep.observe(viewLifecycleOwner) {
|
||||||
|
when (it) {
|
||||||
|
MedtrumViewModel.SetupStep.INITIAL,
|
||||||
|
MedtrumViewModel.SetupStep.PRIMED -> Unit // Nothing to do here, previous state
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
ToastUtils.errorToast(requireContext(), rh.gs(R.string.unexpected_state, it.toString()))
|
||||||
|
aapsLogger.error(LTag.PUMP, "Unexpected state: $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
btnNegative.setOnClickListener {
|
||||||
|
OKDialog.showConfirmation(requireActivity(), rh.gs(R.string.cancel_sure)) {
|
||||||
|
viewModel?.apply {
|
||||||
|
moveStep(PatchStep.CANCEL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package info.nightscout.pump.medtrum.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import dagger.android.support.DaggerAppCompatActivity
|
||||||
|
import info.nightscout.core.ui.R
|
||||||
|
import info.nightscout.pump.medtrum.di.MedtrumPluginQualifier
|
||||||
|
import info.nightscout.rx.AapsSchedulers
|
||||||
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
abstract class MedtrumBaseActivity<B : ViewDataBinding> : DaggerAppCompatActivity(), MedtrumBaseNavigator {
|
||||||
|
@Inject
|
||||||
|
@MedtrumPluginQualifier
|
||||||
|
lateinit var viewModelFactory: ViewModelProvider.Factory
|
||||||
|
|
||||||
|
@Inject lateinit var aapsSchedulers: AapsSchedulers
|
||||||
|
|
||||||
|
protected lateinit var binding: B
|
||||||
|
|
||||||
|
private val compositeDisposable = CompositeDisposable()
|
||||||
|
|
||||||
|
protected lateinit var getResult: ActivityResultLauncher<Intent>
|
||||||
|
|
||||||
|
@LayoutRes
|
||||||
|
abstract fun getLayoutId(): Int
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setTheme(R.style.AppTheme_NoActionBar)
|
||||||
|
|
||||||
|
binding = DataBindingUtil.setContentView(this, getLayoutId())
|
||||||
|
binding.lifecycleOwner = this
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun back() {
|
||||||
|
if(supportFragmentManager.backStackEntryCount == 0) {
|
||||||
|
finish()
|
||||||
|
} else {
|
||||||
|
supportFragmentManager.popBackStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun finish(finishAffinity: Boolean) {
|
||||||
|
if(finishAffinity) {
|
||||||
|
finishAffinity()
|
||||||
|
} else {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package info.nightscout.pump.medtrum.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import dagger.android.support.DaggerFragment
|
||||||
|
import info.nightscout.pump.medtrum.di.MedtrumPluginQualifier
|
||||||
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
abstract class MedtrumBaseFragment<B : ViewDataBinding> : DaggerFragment(), MedtrumBaseNavigator {
|
||||||
|
@Inject
|
||||||
|
@MedtrumPluginQualifier
|
||||||
|
lateinit var viewModelFactory: ViewModelProvider.Factory
|
||||||
|
|
||||||
|
protected var baseActivity: MedtrumBaseActivity<*>? = null
|
||||||
|
|
||||||
|
protected lateinit var binding: B
|
||||||
|
|
||||||
|
private val compositeDisposable = CompositeDisposable()
|
||||||
|
|
||||||
|
@LayoutRes
|
||||||
|
abstract fun getLayoutId(): Int
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onAttach(context: Context) {
|
||||||
|
super.onAttach(context)
|
||||||
|
if (context is MedtrumBaseActivity<*>) {
|
||||||
|
baseActivity = context
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
binding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false)
|
||||||
|
binding.lifecycleOwner = viewLifecycleOwner
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
compositeDisposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onDetach() {
|
||||||
|
super.onDetach()
|
||||||
|
baseActivity = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun back() {
|
||||||
|
baseActivity?.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun finish(finishAffinity: Boolean) {
|
||||||
|
baseActivity?.finish(finishAffinity)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package info.nightscout.pump.medtrum.ui
|
||||||
|
|
||||||
|
interface MedtrumBaseNavigator {
|
||||||
|
fun back()
|
||||||
|
|
||||||
|
fun finish(finishAffinity: Boolean = false)
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package info.nightscout.pump.medtrum.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import info.nightscout.core.ui.dialogs.OKDialog
|
||||||
|
import info.nightscout.pump.medtrum.R
|
||||||
|
import info.nightscout.pump.medtrum.code.PatchStep
|
||||||
|
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumDeactivatePatchBinding
|
||||||
|
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
|
||||||
|
import info.nightscout.rx.logging.AAPSLogger
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import info.nightscout.shared.interfaces.ResourceHelper
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class MedtrumDeactivatePatchFragment : MedtrumBaseFragment<FragmentMedtrumDeactivatePatchBinding>() {
|
||||||
|
|
||||||
|
@Inject lateinit var aapsLogger: AAPSLogger
|
||||||
|
@Inject lateinit var rh: ResourceHelper
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newInstance(): MedtrumDeactivatePatchFragment = MedtrumDeactivatePatchFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLayoutId(): Int = R.layout.fragment_medtrum_deactivate_patch
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
aapsLogger.debug(LTag.PUMP, "MedtrumDeactivatePatchFragment onViewCreated")
|
||||||
|
binding.apply {
|
||||||
|
viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
|
||||||
|
viewModel?.apply {
|
||||||
|
setupStep.observe(viewLifecycleOwner) {
|
||||||
|
when (it) {
|
||||||
|
MedtrumViewModel.SetupStep.STOPPED -> {
|
||||||
|
moveStep(PatchStep.DEACTIVATION_COMPLETE)
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtrumViewModel.SetupStep.ERROR -> {
|
||||||
|
moveStep(PatchStep.ERROR)
|
||||||
|
updateSetupStep(MedtrumViewModel.SetupStep.START_DEACTIVATION) // Reset setup step
|
||||||
|
binding.textDeactivatingPump.text = rh.gs(R.string.deactivating_error)
|
||||||
|
binding.btnNegative.visibility = View.VISIBLE
|
||||||
|
binding.btnPositive.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Unit // Nothing to do here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deactivatePatch()
|
||||||
|
}
|
||||||
|
btnNegative.setOnClickListener {
|
||||||
|
OKDialog.showConfirmation(requireActivity(), rh.gs(R.string.cancel_sure)) {
|
||||||
|
viewModel?.apply {
|
||||||
|
moveStep(PatchStep.CANCEL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package info.nightscout.pump.medtrum.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import info.nightscout.pump.medtrum.R
|
||||||
|
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumDeactivationCompleteBinding
|
||||||
|
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
|
||||||
|
import info.nightscout.rx.logging.AAPSLogger
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class MedtrumDeactivationCompleteFragment : MedtrumBaseFragment<FragmentMedtrumDeactivationCompleteBinding>() {
|
||||||
|
|
||||||
|
@Inject lateinit var aapsLogger: AAPSLogger
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newInstance(): MedtrumDeactivationCompleteFragment = MedtrumDeactivationCompleteFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLayoutId(): Int = R.layout.fragment_medtrum_deactivation_complete
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
aapsLogger.debug(LTag.PUMP, "MedtrumStartDeactivationFragment onViewCreated")
|
||||||
|
binding.apply {
|
||||||
|
viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
|
||||||
|
viewModel?.apply {
|
||||||
|
// Nothing to do here (yet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package info.nightscout.pump.medtrum.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import info.nightscout.core.ui.toast.ToastUtils
|
||||||
|
import info.nightscout.interfaces.protection.ProtectionCheck
|
||||||
|
import info.nightscout.pump.medtrum.MedtrumPump
|
||||||
|
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumOverviewBinding
|
||||||
|
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumOverviewViewModel
|
||||||
|
import info.nightscout.pump.medtrum.R
|
||||||
|
import info.nightscout.pump.medtrum.code.EventType
|
||||||
|
import info.nightscout.pump.medtrum.code.PatchStep
|
||||||
|
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
|
||||||
|
import info.nightscout.rx.AapsSchedulers
|
||||||
|
import info.nightscout.rx.logging.AAPSLogger
|
||||||
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class MedtrumOverviewFragment : MedtrumBaseFragment<FragmentMedtrumOverviewBinding>() {
|
||||||
|
|
||||||
|
@Inject lateinit var aapsSchedulers: AapsSchedulers
|
||||||
|
@Inject lateinit var aapsLogger: AAPSLogger
|
||||||
|
@Inject lateinit var medtrumPump: MedtrumPump
|
||||||
|
@Inject lateinit var protectionCheck: ProtectionCheck
|
||||||
|
|
||||||
|
private var disposable: CompositeDisposable = CompositeDisposable()
|
||||||
|
|
||||||
|
override fun getLayoutId(): Int = R.layout.fragment_medtrum_overview
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
disposable.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
binding.apply {
|
||||||
|
viewmodel = ViewModelProvider(this@MedtrumOverviewFragment, viewModelFactory).get(MedtrumOverviewViewModel::class.java)
|
||||||
|
viewmodel?.apply {
|
||||||
|
eventHandler.observe(viewLifecycleOwner) { evt ->
|
||||||
|
when (evt.peekContent()) {
|
||||||
|
EventType.CHANGE_PATCH_CLICKED -> requireContext().apply {
|
||||||
|
protectionCheck.queryProtection(
|
||||||
|
requireActivity(),
|
||||||
|
ProtectionCheck.Protection.PREFERENCES,
|
||||||
|
{
|
||||||
|
val nextStep = when {
|
||||||
|
medtrumPump.pumpState > MedtrumPumpState.EJECTED && medtrumPump.pumpState < MedtrumPumpState.STOPPED -> {
|
||||||
|
PatchStep.START_DEACTIVATION
|
||||||
|
}
|
||||||
|
|
||||||
|
medtrumPump.pumpState in listOf(MedtrumPumpState.STOPPED, MedtrumPumpState.NONE) -> {
|
||||||
|
PatchStep.PREPARE_PATCH
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
PatchStep.RETRY_ACTIVATION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startActivity(MedtrumActivity.createIntentFromMenu(this, nextStep))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
EventType.PROFILE_NOT_SET -> ToastUtils.infoToast(requireContext(), R.string.no_profile_selected)
|
||||||
|
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package info.nightscout.pump.medtrum.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import info.nightscout.core.ui.dialogs.OKDialog
|
||||||
|
import info.nightscout.core.ui.toast.ToastUtils
|
||||||
|
import info.nightscout.pump.medtrum.R
|
||||||
|
import info.nightscout.pump.medtrum.code.PatchStep
|
||||||
|
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumPreparePatchConnectBinding
|
||||||
|
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
|
||||||
|
import info.nightscout.rx.logging.AAPSLogger
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import info.nightscout.shared.interfaces.ResourceHelper
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class MedtrumPreparePatchConnectFragment : MedtrumBaseFragment<FragmentMedtrumPreparePatchConnectBinding>() {
|
||||||
|
|
||||||
|
@Inject lateinit var aapsLogger: AAPSLogger
|
||||||
|
@Inject lateinit var rh: ResourceHelper
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newInstance(): MedtrumPreparePatchConnectFragment = MedtrumPreparePatchConnectFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLayoutId(): Int = R.layout.fragment_medtrum_prepare_patch_connect
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
aapsLogger.debug(LTag.PUMP, "MedtrumPreparePatchFragment onViewCreated")
|
||||||
|
binding.apply {
|
||||||
|
viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
|
||||||
|
viewModel?.apply {
|
||||||
|
setupStep.observe(viewLifecycleOwner) {
|
||||||
|
when (it) {
|
||||||
|
MedtrumViewModel.SetupStep.INITIAL -> btnPositive.visibility = View.GONE
|
||||||
|
MedtrumViewModel.SetupStep.FILLED -> btnPositive.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
MedtrumViewModel.SetupStep.ERROR -> {
|
||||||
|
ToastUtils.errorToast(requireContext(), rh.gs(R.string.unexpected_state, it.toString()))
|
||||||
|
moveStep(PatchStep.CANCEL)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
preparePatchConnect()
|
||||||
|
}
|
||||||
|
btnNegative.setOnClickListener {
|
||||||
|
OKDialog.showConfirmation(requireActivity(), rh.gs(R.string.cancel_sure)) {
|
||||||
|
viewModel?.apply {
|
||||||
|
moveStep(PatchStep.CANCEL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package info.nightscout.pump.medtrum.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import info.nightscout.pump.medtrum.R
|
||||||
|
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumPreparePatchBinding
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
|
||||||
|
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
|
||||||
|
import info.nightscout.rx.logging.AAPSLogger
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class MedtrumPreparePatchFragment : MedtrumBaseFragment<FragmentMedtrumPreparePatchBinding>() {
|
||||||
|
|
||||||
|
@Inject lateinit var aapsLogger: AAPSLogger
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newInstance(): MedtrumPreparePatchFragment = MedtrumPreparePatchFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLayoutId(): Int = R.layout.fragment_medtrum_prepare_patch
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
aapsLogger.debug(LTag.PUMP, "MedtrumPreparePatchFragment onViewCreated")
|
||||||
|
binding.apply {
|
||||||
|
viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
|
||||||
|
viewModel?.apply {
|
||||||
|
preparePatch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package info.nightscout.pump.medtrum.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import info.nightscout.core.ui.dialogs.OKDialog
|
||||||
|
import info.nightscout.core.ui.toast.ToastUtils
|
||||||
|
import info.nightscout.pump.medtrum.R
|
||||||
|
import info.nightscout.pump.medtrum.code.PatchStep
|
||||||
|
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumPrimeCompleteBinding
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
|
||||||
|
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
|
||||||
|
import info.nightscout.rx.logging.AAPSLogger
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import info.nightscout.shared.interfaces.ResourceHelper
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class MedtrumPrimeCompleteFragment : MedtrumBaseFragment<FragmentMedtrumPrimeCompleteBinding>() {
|
||||||
|
|
||||||
|
@Inject lateinit var aapsLogger: AAPSLogger
|
||||||
|
@Inject lateinit var rh: ResourceHelper
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newInstance(): MedtrumPrimeCompleteFragment = MedtrumPrimeCompleteFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLayoutId(): Int = R.layout.fragment_medtrum_prime_complete
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
binding.apply {
|
||||||
|
viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
|
||||||
|
viewModel?.apply {
|
||||||
|
setupStep.observe(viewLifecycleOwner) {
|
||||||
|
when (it) {
|
||||||
|
MedtrumViewModel.SetupStep.INITIAL,
|
||||||
|
MedtrumViewModel.SetupStep.PRIMED -> Unit // Nothing to do here, previous state
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
ToastUtils.errorToast(requireContext(), rh.gs(R.string.unexpected_state, it.toString()))
|
||||||
|
aapsLogger.error(LTag.PUMP, "Unexpected state: $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
btnNegative.setOnClickListener {
|
||||||
|
OKDialog.showConfirmation(requireActivity(), rh.gs(R.string.cancel_sure)) {
|
||||||
|
viewModel?.apply {
|
||||||
|
moveStep(PatchStep.CANCEL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package info.nightscout.pump.medtrum.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import info.nightscout.core.ui.dialogs.OKDialog
|
||||||
|
import info.nightscout.core.ui.toast.ToastUtils
|
||||||
|
import info.nightscout.pump.medtrum.R
|
||||||
|
import info.nightscout.pump.medtrum.code.PatchStep
|
||||||
|
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumPrimeBinding
|
||||||
|
import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
|
||||||
|
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
|
||||||
|
import info.nightscout.rx.logging.AAPSLogger
|
||||||
|
import info.nightscout.rx.logging.LTag
|
||||||
|
import info.nightscout.shared.interfaces.ResourceHelper
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class MedtrumPrimeFragment : MedtrumBaseFragment<FragmentMedtrumPrimeBinding>() {
|
||||||
|
|
||||||
|
@Inject lateinit var aapsLogger: AAPSLogger
|
||||||
|
@Inject lateinit var rh: ResourceHelper
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newInstance(): MedtrumPrimeFragment = MedtrumPrimeFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLayoutId(): Int = R.layout.fragment_medtrum_prime
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
binding.apply {
|
||||||
|
viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MedtrumViewModel::class.java]
|
||||||
|
viewModel?.apply {
|
||||||
|
setupStep.observe(viewLifecycleOwner) {
|
||||||
|
when (it) {
|
||||||
|
MedtrumViewModel.SetupStep.INITIAL,
|
||||||
|
MedtrumViewModel.SetupStep.FILLED -> Unit // Nothing to do here, previous state
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
ToastUtils.errorToast(requireContext(), rh.gs(R.string.unexpected_state, it.toString()))
|
||||||
|
aapsLogger.error(LTag.PUMP, "Unexpected state: $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
btnNegative.setOnClickListener {
|
||||||
|
OKDialog.showConfirmation(requireActivity(), rh.gs(R.string.cancel_sure)) {
|
||||||
|
viewModel?.apply {
|
||||||
|
moveStep(PatchStep.CANCEL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue