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:diaconn')
|
||||
implementation project(':pump:eopatch')
|
||||
implementation project(':pump:medtrum')
|
||||
implementation project(':insight')
|
||||
implementation project(':pump:medtronic')
|
||||
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.combov2.ComboV2Plugin
|
||||
import info.nightscout.pump.diaconn.DiaconnG8Plugin
|
||||
import info.nightscout.pump.medtrum.MedtrumPlugin
|
||||
import info.nightscout.pump.virtual.VirtualPumpPlugin
|
||||
import info.nightscout.rx.bus.RxBus
|
||||
import info.nightscout.rx.events.EventPreferenceChange
|
||||
|
@ -122,6 +123,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
|
|||
@Inject lateinit var wearPlugin: WearPlugin
|
||||
@Inject lateinit var maintenancePlugin: MaintenancePlugin
|
||||
@Inject lateinit var eopatchPumpPlugin: EopatchPumpPlugin
|
||||
@Inject lateinit var medtrumPlugin: MedtrumPlugin
|
||||
|
||||
@Inject lateinit var passwordCheck: PasswordCheck
|
||||
@Inject lateinit var nsSettingStatus: NSSettingsStatus
|
||||
|
@ -212,6 +214,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
|
|||
addPreferencesFromResourceIfEnabled(medtronicPumpPlugin, rootKey, config.PUMPDRIVERS)
|
||||
addPreferencesFromResourceIfEnabled(diaconnG8Plugin, rootKey, config.PUMPDRIVERS)
|
||||
addPreferencesFromResourceIfEnabled(eopatchPumpPlugin, rootKey, config.PUMPDRIVERS)
|
||||
addPreferencesFromResourceIfEnabled(medtrumPlugin, rootKey, config.PUMPDRIVERS)
|
||||
addPreferencesFromResource(R.xml.pref_pump, rootKey, config.PUMPDRIVERS)
|
||||
addPreferencesFromResourceIfEnabled(virtualPumpPlugin, 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.DanaModule
|
||||
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.virtual.di.VirtualPumpModule
|
||||
import info.nightscout.rx.di.RxModule
|
||||
|
@ -87,6 +88,7 @@ import javax.inject.Singleton
|
|||
OmnipodErosModule::class,
|
||||
PumpCommonModule::class,
|
||||
RileyLinkModule::class,
|
||||
MedtrumModule::class,
|
||||
VirtualPumpModule::class
|
||||
]
|
||||
)
|
||||
|
|
|
@ -46,6 +46,7 @@ import info.nightscout.plugins.sync.tidepool.TidepoolPlugin
|
|||
import info.nightscout.plugins.sync.xdrip.XdripPlugin
|
||||
import info.nightscout.pump.combo.ComboPlugin
|
||||
import info.nightscout.pump.combov2.ComboV2Plugin
|
||||
import info.nightscout.pump.medtrum.MedtrumPlugin
|
||||
import info.nightscout.pump.diaconn.DiaconnG8Plugin
|
||||
import info.nightscout.pump.virtual.VirtualPumpPlugin
|
||||
import info.nightscout.sensitivity.SensitivityAAPSPlugin
|
||||
|
@ -209,6 +210,12 @@ abstract class PluginsListModule {
|
|||
@IntKey(156)
|
||||
abstract fun bindEopatchPumpPlugin(plugin: EopatchPumpPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@PumpDriver
|
||||
@IntoMap
|
||||
@IntKey(160)
|
||||
abstract fun bindMedtrumPlugin(plugin: MedtrumPlugin): PluginBase
|
||||
|
||||
@Binds
|
||||
@AllConfigs
|
||||
@IntoMap
|
||||
|
|
|
@ -131,9 +131,12 @@ open class Notification {
|
|||
const val IDENTIFICATION_NOT_SET = 77
|
||||
const val PERMISSION_BT = 78
|
||||
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 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
|
||||
|
||||
|
|
|
@ -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) {
|
||||
AAPS("AAPS"),
|
||||
Medtrum("Medtrum"),
|
||||
Medtronic("Medtronic"),
|
||||
Sooil("SOOIL"),
|
||||
Tandem("Tandem"),
|
||||
|
|
|
@ -26,6 +26,7 @@ enum class PumpCapability {
|
|||
YpsomedCapabilities(arrayOf(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, ReplaceBattery, TDD, ManualTDDLoad)), // BasalRates (separately grouped)
|
||||
DiaconnCapabilities(arrayOf(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, ReplaceBattery, TDD, ManualTDDLoad)), //
|
||||
EopatchCapabilities(arrayOf(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, BasalRate30min)),
|
||||
MedtrumCapabilities(arrayOf(Bolus, TempBasal, BasalProfileSet, BasalRate30min, TDD)), // Technically the pump supports ExtendedBolus, but not implemented (yet)
|
||||
BasalRate_Duration15minAllowed,
|
||||
BasalRate_Duration30minAllowed,
|
||||
BasalRate_Duration15and30minAllowed(arrayOf(BasalRate_Duration15minAllowed, BasalRate_Duration30minAllowed)),
|
||||
|
|
|
@ -391,7 +391,29 @@ enum class PumpType {
|
|||
isPatchPump = true,
|
||||
maxReservoirReading = 50,
|
||||
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
|
||||
var manufacturer: ManufacturerType? = null
|
||||
|
@ -458,6 +480,7 @@ enum class PumpType {
|
|||
OmnipodEros,
|
||||
OmnipodDash,
|
||||
EOPatch2,
|
||||
Medtrum,
|
||||
MDI,
|
||||
VirtualPump,
|
||||
Unknown
|
||||
|
|
|
@ -23,11 +23,14 @@ abstract class Command(
|
|||
BASAL_PROFILE,
|
||||
READSTATUS,
|
||||
LOAD_HISTORY, // TDDs and so far only Dana specific
|
||||
LOAD_EVENTS, // so far only Dana specific
|
||||
LOAD_EVENTS,
|
||||
LOAD_TDD,
|
||||
SET_USER_SETTINGS, // so far only Dana specific,
|
||||
START_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
|
||||
CUSTOM_COMMAND
|
||||
}
|
||||
|
|
|
@ -31,6 +31,9 @@ interface CommandQueue {
|
|||
fun setUserOptions(callback: Callback?): Boolean
|
||||
fun loadTDDs(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 isCustomCommandRunning(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.DIACONN_G8 -> PumpType.DIACONN_G8
|
||||
InterfaceIDs.PumpType.EOPATCH2 -> PumpType.EOFLOW_EOPATCH2
|
||||
InterfaceIDs.PumpType.MEDTRUM -> PumpType.MEDTRUM_NANO
|
||||
InterfaceIDs.PumpType.MEDTRUM_UNTESTED -> PumpType.MEDTRUM_UNTESTED
|
||||
InterfaceIDs.PumpType.CACHE -> PumpType.CACHE
|
||||
}
|
||||
|
||||
|
@ -117,5 +119,7 @@ fun PumpType.toDbPumpType(): InterfaceIDs.PumpType =
|
|||
PumpType.USER -> InterfaceIDs.PumpType.USER
|
||||
PumpType.DIACONN_G8 -> InterfaceIDs.PumpType.DIACONN_G8
|
||||
PumpType.EOFLOW_EOPATCH2 -> InterfaceIDs.PumpType.EOPATCH2
|
||||
PumpType.MEDTRUM_NANO -> InterfaceIDs.PumpType.MEDTRUM
|
||||
PumpType.MEDTRUM_UNTESTED -> InterfaceIDs.PumpType.MEDTRUM_UNTESTED
|
||||
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="extended_bolus_u_min">EXTENDED BOLUS %1$.2f U %2$d min</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_tdds">LOAD TDDs</string>
|
||||
<string name="set_profile">SET PROFILE</string>
|
||||
|
|
|
@ -43,6 +43,8 @@ data class InterfaceIDs(
|
|||
MDI,
|
||||
DIACONN_G8,
|
||||
EOPATCH2,
|
||||
MEDTRUM,
|
||||
MEDTRUM_UNTESTED,
|
||||
USER,
|
||||
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.CommandCancelExtendedBolus
|
||||
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.CommandDeactivate
|
||||
import info.nightscout.implementation.queue.commands.CommandExtendedBolus
|
||||
import info.nightscout.implementation.queue.commands.CommandInsightSetTBROverNotification
|
||||
import info.nightscout.implementation.queue.commands.CommandLoadEvents
|
||||
import info.nightscout.implementation.queue.commands.CommandLoadHistory
|
||||
import info.nightscout.implementation.queue.commands.CommandLoadTDDs
|
||||
import info.nightscout.implementation.queue.commands.CommandUpdateTime
|
||||
|
||||
@Module
|
||||
@Suppress("unused")
|
||||
|
@ -32,6 +35,9 @@ abstract class CommandQueueModule {
|
|||
@ContributesAndroidInjector abstract fun commandExtendedBolusInjector(): CommandExtendedBolus
|
||||
@ContributesAndroidInjector abstract fun commandInsightSetTBROverNotificationInjector(): CommandInsightSetTBROverNotification
|
||||
@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 commandLoadTDDsInjector(): CommandLoadTDDs
|
||||
@ContributesAndroidInjector abstract fun commandReadStatusInjector(): CommandReadStatus
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
package info.nightscout.implementation.pump
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import info.nightscout.androidaps.annotations.OpenForTesting
|
||||
import info.nightscout.implementation.R
|
||||
import info.nightscout.interfaces.pump.DetailedBolusInfo
|
||||
import info.nightscout.interfaces.pump.DetailedBolusInfoStorage
|
||||
import info.nightscout.rx.logging.AAPSLogger
|
||||
import info.nightscout.shared.sharedPreferences.SP
|
||||
import info.nightscout.rx.logging.LTag
|
||||
import info.nightscout.shared.interfaces.ResourceHelper
|
||||
import info.nightscout.shared.utils.T
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
@ -14,10 +18,12 @@ import kotlin.math.abs
|
|||
@OpenForTesting
|
||||
@Singleton
|
||||
class DetailedBolusInfoStorageImpl @Inject constructor(
|
||||
val aapsLogger: AAPSLogger
|
||||
val aapsLogger: AAPSLogger,
|
||||
val sp: SP,
|
||||
val rh: ResourceHelper
|
||||
) : DetailedBolusInfoStorage {
|
||||
|
||||
val store = ArrayList<DetailedBolusInfo>()
|
||||
val store = loadStore()
|
||||
|
||||
fun DetailedBolusInfo.toJsonString(): String = Gson().toJson(this)
|
||||
|
||||
|
@ -25,6 +31,7 @@ class DetailedBolusInfoStorageImpl @Inject constructor(
|
|||
override fun add(detailedBolusInfo: DetailedBolusInfo) {
|
||||
aapsLogger.debug("Stored bolus info: ${detailedBolusInfo.toJsonString()}")
|
||||
store.add(detailedBolusInfo)
|
||||
saveStore()
|
||||
}
|
||||
|
||||
@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) {
|
||||
aapsLogger.debug(LTag.PUMP, "Using & removing bolus info for time $bolusTime: ${store[i]}")
|
||||
store.removeAt(i)
|
||||
saveStore()
|
||||
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) {
|
||||
aapsLogger.debug(LTag.PUMP, "Using TIME-ONLY & removing bolus info for time $bolusTime: ${store[i]}")
|
||||
store.removeAt(i)
|
||||
saveStore()
|
||||
return d
|
||||
}
|
||||
}
|
||||
|
@ -61,4 +70,24 @@ class DetailedBolusInfoStorageImpl @Inject constructor(
|
|||
aapsLogger.debug(LTag.PUMP, "Bolus info not found for time $bolusTime")
|
||||
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.CommandCancelExtendedBolus
|
||||
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.CommandDeactivate
|
||||
import info.nightscout.implementation.queue.commands.CommandExtendedBolus
|
||||
import info.nightscout.implementation.queue.commands.CommandInsightSetTBROverNotification
|
||||
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.CommandTempBasalAbsolute
|
||||
import info.nightscout.implementation.queue.commands.CommandTempBasalPercent
|
||||
import info.nightscout.implementation.queue.commands.CommandUpdateTime
|
||||
import info.nightscout.interfaces.AndroidPermission
|
||||
import info.nightscout.interfaces.Config
|
||||
import info.nightscout.interfaces.constraints.Constraint
|
||||
|
@ -535,6 +538,46 @@ class CommandQueueImplementation @Inject constructor(
|
|||
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 {
|
||||
if (isCustomCommandInQueue(customCommand.javaClass)) {
|
||||
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.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
|
||||
|
@ -32,6 +33,13 @@ class CommandLoadEvents(
|
|||
aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
|
||||
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)
|
||||
|
|
|
@ -4,6 +4,7 @@ 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
|
||||
|
@ -30,6 +31,12 @@ class CommandSetUserSettings(
|
|||
aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
|
||||
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)
|
||||
|
|
|
@ -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"?>
|
||||
<resources>
|
||||
<string name="key_bolus_storage" translatable="false">key_bolus_storage</string>
|
||||
<string name="bg_label">BG</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.interfaces.pump.DetailedBolusInfo
|
||||
import info.nightscout.shared.interfaces.ResourceHelper
|
||||
import info.nightscout.shared.sharedPreferences.SP
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mockito.Mock
|
||||
|
||||
class DetailedBolusInfoStorageTest : TestBase() {
|
||||
|
||||
@Mock lateinit var sp: SP
|
||||
@Mock lateinit var rh: ResourceHelper
|
||||
|
||||
private val info1 = DetailedBolusInfo()
|
||||
private val info2 = DetailedBolusInfo()
|
||||
private val info3 = DetailedBolusInfo()
|
||||
|
@ -26,7 +32,7 @@ class DetailedBolusInfoStorageTest : TestBase() {
|
|||
|
||||
@BeforeEach
|
||||
fun prepare() {
|
||||
detailedBolusInfoStorage = DetailedBolusInfoStorageImpl(aapsLogger)
|
||||
detailedBolusInfoStorage = DetailedBolusInfoStorageImpl(aapsLogger, sp, rh)
|
||||
}
|
||||
|
||||
private fun setUp() {
|
||||
|
|
|
@ -238,6 +238,19 @@ class CommandQueueImplementationTest : TestBaseWithProfile() {
|
|||
// add loadEvents
|
||||
commandQueue.loadEvents(null)
|
||||
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.tempBasalAbsolute(0.0, 30, true, validProfile, PumpSync.TemporaryBasalType.NORMAL, null)
|
||||
commandQueue.pickup()
|
||||
|
@ -354,6 +367,54 @@ class CommandQueueImplementationTest : TestBaseWithProfile() {
|
|||
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
|
||||
fun isLoadTDDsCommandInQueue() {
|
||||
// given
|
||||
|
|
|
@ -35,6 +35,7 @@ import info.nightscout.interfaces.plugin.ActivePlugin
|
|||
import info.nightscout.interfaces.plugin.PluginBase
|
||||
import info.nightscout.interfaces.plugin.PluginType
|
||||
import info.nightscout.interfaces.profile.ProfileFunction
|
||||
import info.nightscout.interfaces.pump.Medtrum
|
||||
import info.nightscout.interfaces.pump.OmnipodDash
|
||||
import info.nightscout.interfaces.pump.OmnipodEros
|
||||
import info.nightscout.interfaces.queue.CommandQueue
|
||||
|
@ -319,23 +320,24 @@ class SWDefinition @Inject constructor(
|
|||
.text(R.string.readstatus)
|
||||
.action { commandQueue.readStatus(rh.gs(info.nightscout.core.ui.R.string.clicked_connect_to_pump), null) }
|
||||
.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
|
||||
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)
|
||||
.visibility { activePlugin.activePump !is OmnipodEros && activePlugin.activePump !is OmnipodDash })
|
||||
.visibility { activePlugin.activePump !is OmnipodEros && activePlugin.activePump !is OmnipodDash && activePlugin.activePump !is Medtrum })
|
||||
.validator { isPumpInitialized() }
|
||||
|
||||
private fun isPumpInitialized(): Boolean {
|
||||
val activePump = activePlugin.activePump
|
||||
|
||||
// For Omnipod, activating a Pod can be done after setup through the Omnipod fragment
|
||||
// For the Eros model, consider the pump initialized when a RL has been configured successfully
|
||||
// For Dash model, consider the pump setup without any extra conditions
|
||||
// For Omnipod and Medtrum, activating a Pod/Patch can be done after setup through the pump fragment
|
||||
// For the Eros, consider the pump initialized when a RL has been configured successfully
|
||||
// For all others, consider the pump setup without any extra conditions
|
||||
return activePump.isInitialized()
|
||||
|| (activePump is OmnipodEros && activePump.isRileyLinkReady())
|
||||
|| activePump is OmnipodDash
|
||||
|| activePump is Medtrum
|
||||
}
|
||||
|
||||
private val screenAps
|
||||
|
|
|
@ -2226,7 +2226,7 @@ class ComboV2Plugin @Inject constructor (
|
|||
// only shows up in the Combo fragment.
|
||||
if (newState == DriverState.Suspended) {
|
||||
uiInteraction.addNotification(
|
||||
Notification.COMBO_PUMP_SUSPENDED,
|
||||
Notification.PUMP_SUSPENDED,
|
||||
text = rh.gs(R.string.combov2_pump_is_suspended),
|
||||
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