Add Profile to BasalProgram mapper function, add some preliminary code in OmnipodDashManagerImpl
This commit is contained in:
parent
29c0b62978
commit
dbff1c6e50
5 changed files with 260 additions and 12 deletions
|
@ -3,6 +3,10 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.OmnipodDashBleManager
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.OmnipodDashBleManager
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEvent
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEvent
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEventType
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.GetVersionCommand
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.GetVersionCommand.Companion.DEFAULT_UNIQUE_ID
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.ActivationProgress
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.AlertConfiguration
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.AlertConfiguration
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.AlertSlot
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.AlertSlot
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BasalProgram
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BasalProgram
|
||||||
|
@ -19,9 +23,36 @@ class OmnipodDashManagerImpl @Inject constructor(
|
||||||
private val bleManager: OmnipodDashBleManager
|
private val bleManager: OmnipodDashBleManager
|
||||||
) : OmnipodDashManager {
|
) : OmnipodDashManager {
|
||||||
|
|
||||||
|
private val observePodReadyForActivationPart1: Observable<PodEvent>
|
||||||
|
get() {
|
||||||
|
if (podStateManager.activationProgress.isBefore(ActivationProgress.PHASE_1_COMPLETED)) {
|
||||||
|
return Observable.empty()
|
||||||
|
}
|
||||||
|
return Observable.error(IllegalStateException("Pod is in an incorrect state"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private val observeConnectToPod: Observable<PodEvent> = Observable.defer {
|
||||||
|
// TODO
|
||||||
|
// send CONNECTING event here
|
||||||
|
bleManager.connect()
|
||||||
|
Observable.just(PodEvent(PodEventType.CONNECTED, null))
|
||||||
|
}
|
||||||
|
|
||||||
override fun activatePodPart1(): Observable<PodEvent> {
|
override fun activatePodPart1(): Observable<PodEvent> {
|
||||||
// TODO
|
// TODO
|
||||||
return Observable.empty()
|
return Observable.concat(
|
||||||
|
observePodReadyForActivationPart1,
|
||||||
|
observeConnectToPod,
|
||||||
|
Observable.defer {
|
||||||
|
bleManager.sendCommand(GetVersionCommand.Builder() //
|
||||||
|
.setSequenceNumber(podStateManager.messageSequenceNumber) //
|
||||||
|
.setUniqueId(DEFAULT_UNIQUE_ID) //
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
Observable.just(PodEvent(PodEventType.COMMAND_SENT, null))
|
||||||
|
}
|
||||||
|
// ... Send more commands
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun activatePodPart2(): Observable<PodEvent> {
|
override fun activatePodPart2(): Observable<PodEvent> {
|
||||||
|
|
|
@ -19,7 +19,11 @@ class BasalProgram(
|
||||||
|
|
||||||
fun rateAt(date: Date): Double = 0.0 // TODO
|
fun rateAt(date: Date): Double = 0.0 // TODO
|
||||||
|
|
||||||
class Segment(val startSlotIndex: Short, val endSlotIndex: Short, val basalRateInHundredthUnitsPerHour: Int) {
|
class Segment(
|
||||||
|
val startSlotIndex: Short,
|
||||||
|
val endSlotIndex: Short,
|
||||||
|
val basalRateInHundredthUnitsPerHour: Int
|
||||||
|
) {
|
||||||
|
|
||||||
fun getPulsesPerHour(): Short {
|
fun getPulsesPerHour(): Short {
|
||||||
return (basalRateInHundredthUnitsPerHour * PULSES_PER_UNIT / 100).toShort()
|
return (basalRateInHundredthUnitsPerHour * PULSES_PER_UNIT / 100).toShort()
|
||||||
|
@ -37,6 +41,26 @@ class BasalProgram(
|
||||||
'}'
|
'}'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as Segment
|
||||||
|
|
||||||
|
if (startSlotIndex != other.startSlotIndex) return false
|
||||||
|
if (endSlotIndex != other.endSlotIndex) return false
|
||||||
|
if (basalRateInHundredthUnitsPerHour != other.basalRateInHundredthUnitsPerHour) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result: Int = startSlotIndex.toInt()
|
||||||
|
result = 31 * result + endSlotIndex
|
||||||
|
result = 31 * result + basalRateInHundredthUnitsPerHour
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val PULSES_PER_UNIT: Byte = 20
|
private const val PULSES_PER_UNIT: Byte = 20
|
||||||
|
@ -48,4 +72,19 @@ class BasalProgram(
|
||||||
"segments=" + segments +
|
"segments=" + segments +
|
||||||
'}'
|
'}'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as BasalProgram
|
||||||
|
|
||||||
|
if (segments != other.segments) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return segments.hashCode()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.ui.wizard.activation.viewmodel.action
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.ui.wizard.activation.viewmodel.action
|
||||||
|
|
||||||
import android.os.AsyncTask
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import dagger.android.HasAndroidInjector
|
import dagger.android.HasAndroidInjector
|
||||||
import info.nightscout.androidaps.data.PumpEnactResult
|
import info.nightscout.androidaps.data.PumpEnactResult
|
||||||
|
@ -8,12 +7,12 @@ import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
import info.nightscout.androidaps.logging.LTag
|
import info.nightscout.androidaps.logging.LTag
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.common.ui.wizard.activation.viewmodel.action.InitializePodViewModel
|
import info.nightscout.androidaps.plugins.pump.omnipod.common.ui.wizard.activation.viewmodel.action.InitializePodViewModel
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.R
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.R
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.OmnipodDashBleManager
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.OmnipodDashManager
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class DashInitializePodViewModel @Inject constructor(private val aapsLogger: AAPSLogger,
|
class DashInitializePodViewModel @Inject constructor(private val aapsLogger: AAPSLogger,
|
||||||
private val injector: HasAndroidInjector,
|
private val injector: HasAndroidInjector,
|
||||||
private val bleManager: OmnipodDashBleManager) : InitializePodViewModel() {
|
private val omnipodManager: OmnipodDashManager) : InitializePodViewModel() {
|
||||||
|
|
||||||
override fun isPodInAlarm(): Boolean = false // TODO
|
override fun isPodInAlarm(): Boolean = false // TODO
|
||||||
|
|
||||||
|
@ -23,13 +22,11 @@ class DashInitializePodViewModel @Inject constructor(private val aapsLogger: AAP
|
||||||
|
|
||||||
override fun doExecuteAction(): PumpEnactResult {
|
override fun doExecuteAction(): PumpEnactResult {
|
||||||
// TODO FIRST STEP OF ACTIVATION
|
// TODO FIRST STEP OF ACTIVATION
|
||||||
AsyncTask.execute {
|
val disposable = omnipodManager.activatePodPart1().subscribe(
|
||||||
try {
|
{ podEvent -> aapsLogger.debug(LTag.PUMP, "Received PodEvent in Pod activation part 1: $podEvent") },
|
||||||
bleManager.activateNewPod()
|
{ throwable -> aapsLogger.error(LTag.PUMP, "Error in Pod activation part 1: $throwable") },
|
||||||
} catch (e: Exception) {
|
{ aapsLogger.debug("Pod activation part 1 completed") }
|
||||||
aapsLogger.error(LTag.PUMP, "TEST ACTIVATE Exception" + e.toString())
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return PumpEnactResult(injector).success(false).comment("not implemented")
|
return PumpEnactResult(injector).success(false).comment("not implemented")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.util
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.data.Profile
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BasalProgram
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
fun mapProfileToBasalProgram(profile: Profile): BasalProgram {
|
||||||
|
val basalValues = profile.basalValues
|
||||||
|
?: throw IllegalArgumentException("Basal values can not be null")
|
||||||
|
if (basalValues.isEmpty()) {
|
||||||
|
throw IllegalArgumentException("Basal values should contain values")
|
||||||
|
}
|
||||||
|
|
||||||
|
val entries: MutableList<BasalProgram.Segment> = ArrayList()
|
||||||
|
|
||||||
|
var previousBasalValue: Profile.ProfileValue? = null
|
||||||
|
|
||||||
|
for (basalValue in basalValues) {
|
||||||
|
if (basalValue.timeAsSeconds >= 86_400) {
|
||||||
|
throw IllegalArgumentException("Basal segment start time can not be greater than 86400")
|
||||||
|
}
|
||||||
|
if (basalValue.timeAsSeconds < 0) {
|
||||||
|
throw IllegalArgumentException("Basal segment start time can not be less than 0")
|
||||||
|
}
|
||||||
|
if (basalValue.timeAsSeconds % 1_800 != 0) {
|
||||||
|
throw IllegalArgumentException("Basal segment time should be dividable by 30 minutes")
|
||||||
|
}
|
||||||
|
|
||||||
|
val startSlotIndex = (basalValue.timeAsSeconds / 1800).toShort()
|
||||||
|
|
||||||
|
if (previousBasalValue != null) {
|
||||||
|
entries.add(
|
||||||
|
BasalProgram.Segment(
|
||||||
|
(previousBasalValue!!.timeAsSeconds / 1800).toShort(),
|
||||||
|
startSlotIndex,
|
||||||
|
(PumpType.Omnipod_Dash.determineCorrectBasalSize(previousBasalValue.value) * 100).roundToInt()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entries.size == 0 && basalValue.timeAsSeconds != 0) {
|
||||||
|
throw java.lang.IllegalArgumentException("First basal segment start time should be 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entries.size > 0 && entries[entries.size - 1].endSlotIndex != startSlotIndex) {
|
||||||
|
throw IllegalArgumentException("Illegal start time for basal segment: does not match previous previous segment's end time")
|
||||||
|
}
|
||||||
|
|
||||||
|
previousBasalValue = basalValue
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.add(
|
||||||
|
BasalProgram.Segment(
|
||||||
|
(previousBasalValue!!.timeAsSeconds / 1800).toShort(),
|
||||||
|
48,
|
||||||
|
(PumpType.Omnipod_Dash.determineCorrectBasalSize(previousBasalValue.value) * 100).roundToInt()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return BasalProgram(entries)
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.util
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.data.Profile
|
||||||
|
import info.nightscout.androidaps.data.Profile.ProfileValue
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BasalProgram
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.ExpectedException
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.powermock.api.mockito.PowerMockito
|
||||||
|
|
||||||
|
class FunctionsTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField var thrown = ExpectedException.none()
|
||||||
|
|
||||||
|
@Test fun validProfile() {
|
||||||
|
val profile = Mockito.mock(Profile::class.java)
|
||||||
|
val value1 = Mockito.mock(ProfileValue::class.java)
|
||||||
|
value1.timeAsSeconds = 0
|
||||||
|
value1.value = 0.5
|
||||||
|
val value2 = Mockito.mock(ProfileValue::class.java)
|
||||||
|
value2.timeAsSeconds = 18000
|
||||||
|
value2.value = 1.0
|
||||||
|
val value3 = Mockito.mock(ProfileValue::class.java)
|
||||||
|
value3.timeAsSeconds = 50400
|
||||||
|
value3.value = 3.05
|
||||||
|
PowerMockito.`when`(profile.basalValues).thenReturn(arrayOf(
|
||||||
|
value1,
|
||||||
|
value2,
|
||||||
|
value3
|
||||||
|
))
|
||||||
|
val basalProgram: BasalProgram = mapProfileToBasalProgram(profile)
|
||||||
|
val entries: List<BasalProgram.Segment> = basalProgram.segments
|
||||||
|
assertEquals(3, entries.size)
|
||||||
|
val entry1: BasalProgram.Segment = entries[0]
|
||||||
|
assertEquals(0.toShort(), entry1.startSlotIndex)
|
||||||
|
assertEquals(50, entry1.basalRateInHundredthUnitsPerHour)
|
||||||
|
assertEquals(10.toShort(), entry1.endSlotIndex)
|
||||||
|
val entry2: BasalProgram.Segment = entries[1]
|
||||||
|
assertEquals(10.toShort(), entry2.startSlotIndex)
|
||||||
|
assertEquals(100, entry2.basalRateInHundredthUnitsPerHour)
|
||||||
|
assertEquals(28.toShort(), entry2.endSlotIndex)
|
||||||
|
val entry3: BasalProgram.Segment = entries[2]
|
||||||
|
assertEquals(28.toShort(), entry3.startSlotIndex)
|
||||||
|
assertEquals(305, entry3.basalRateInHundredthUnitsPerHour)
|
||||||
|
assertEquals(48.toShort(), entry3.endSlotIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun invalidProfileNullEntries() {
|
||||||
|
thrown.expect(IllegalArgumentException::class.java)
|
||||||
|
thrown.expectMessage("Basal values can not be null")
|
||||||
|
mapProfileToBasalProgram(Mockito.mock(Profile::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun invalidProfileZeroEntries() {
|
||||||
|
thrown.expect(IllegalArgumentException::class.java)
|
||||||
|
thrown.expectMessage("Basal values should contain values")
|
||||||
|
val profile = Mockito.mock(Profile::class.java)
|
||||||
|
PowerMockito.`when`(profile.basalValues).thenReturn(arrayOfNulls(0))
|
||||||
|
mapProfileToBasalProgram(profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun invalidProfileNonZeroOffset() {
|
||||||
|
thrown.expect(IllegalArgumentException::class.java)
|
||||||
|
thrown.expectMessage("First basal segment start time should be 0")
|
||||||
|
val profile = Mockito.mock(Profile::class.java)
|
||||||
|
val value = Mockito.mock(ProfileValue::class.java)
|
||||||
|
value.timeAsSeconds = 1800
|
||||||
|
value.value = 0.5
|
||||||
|
PowerMockito.`when`(profile.basalValues).thenReturn(arrayOf(
|
||||||
|
value))
|
||||||
|
mapProfileToBasalProgram(profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun invalidProfileMoreThan24Hours() {
|
||||||
|
thrown.expect(IllegalArgumentException::class.java)
|
||||||
|
thrown.expectMessage("Basal segment start time can not be greater than 86400")
|
||||||
|
|
||||||
|
val profile = Mockito.mock(Profile::class.java)
|
||||||
|
val value1 = Mockito.mock(ProfileValue::class.java)
|
||||||
|
value1.timeAsSeconds = 0
|
||||||
|
value1.value = 0.5
|
||||||
|
val value2 = Mockito.mock(ProfileValue::class.java)
|
||||||
|
value2.timeAsSeconds = 86400
|
||||||
|
value2.value = 0.5
|
||||||
|
PowerMockito.`when`(profile.basalValues).thenReturn(arrayOf(
|
||||||
|
value1,
|
||||||
|
value2
|
||||||
|
))
|
||||||
|
mapProfileToBasalProgram(profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun invalidProfileNegativeOffset() {
|
||||||
|
thrown.expect(IllegalArgumentException::class.java)
|
||||||
|
thrown.expectMessage("Basal segment start time can not be less than 0")
|
||||||
|
val profile = Mockito.mock(Profile::class.java)
|
||||||
|
val value = Mockito.mock(ProfileValue::class.java)
|
||||||
|
value.timeAsSeconds = -1
|
||||||
|
value.value = 0.5
|
||||||
|
PowerMockito.`when`(profile.basalValues).thenReturn(arrayOf(
|
||||||
|
value))
|
||||||
|
mapProfileToBasalProgram(profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun roundsToSupportedPrecision() {
|
||||||
|
val profile = Mockito.mock(Profile::class.java)
|
||||||
|
val value = Mockito.mock(ProfileValue::class.java)
|
||||||
|
value.timeAsSeconds = 0
|
||||||
|
value.value = 0.04
|
||||||
|
PowerMockito.`when`(profile.basalValues).thenReturn(arrayOf(
|
||||||
|
value))
|
||||||
|
val basalProgram: BasalProgram = mapProfileToBasalProgram(profile)
|
||||||
|
val basalProgramElement: BasalProgram.Segment = basalProgram.segments[0]
|
||||||
|
assertEquals(5, basalProgramElement.basalRateInHundredthUnitsPerHour)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue