Merge pull request #3008 from buessow/dev-garmin
Garmin: Fix glucose unites and widget support
This commit is contained in:
commit
9700ccd5a0
5 changed files with 256 additions and 14 deletions
|
@ -94,6 +94,8 @@ class GarminPlugin @Inject constructor(
|
||||||
server?.close()
|
server?.close()
|
||||||
server = HttpServer(aapsLogger, port).apply {
|
server = HttpServer(aapsLogger, port).apply {
|
||||||
registerEndpoint("/get", ::onGetBloodGlucose)
|
registerEndpoint("/get", ::onGetBloodGlucose)
|
||||||
|
registerEndpoint("/carbs", ::onPostCarbs)
|
||||||
|
registerEndpoint("/connect", ::onConnectPump)
|
||||||
}
|
}
|
||||||
} else if (server != null) {
|
} else if (server != null) {
|
||||||
aapsLogger.info(LTag.GARMIN, "stopping HTTP server")
|
aapsLogger.info(LTag.GARMIN, "stopping HTTP server")
|
||||||
|
@ -243,4 +245,36 @@ class GarminPlugin @Inject constructor(
|
||||||
aapsLogger.warn(LTag.GARMIN, "Skip saving invalid HR $avg $samplingStart..$samplingEnd")
|
aapsLogger.warn(LTag.GARMIN, "Skip saving invalid HR $avg $samplingStart..$samplingEnd")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Handles carb notification from the device. */
|
||||||
|
@VisibleForTesting
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
fun onPostCarbs(caller: SocketAddress, uri: URI, requestBody: String?): CharSequence {
|
||||||
|
aapsLogger.info(LTag.GARMIN, "carbs from $caller, req: $uri")
|
||||||
|
postCarbs(getQueryParameter(uri, "carbs", 0L).toInt())
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun postCarbs(carbs: Int) {
|
||||||
|
if (carbs > 0) {
|
||||||
|
loopHub.postCarbs(carbs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Handles pump connected notification that the user entered on the Garmin device. */
|
||||||
|
@VisibleForTesting
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
fun onConnectPump(caller: SocketAddress, uri: URI, requestBody: String?): CharSequence {
|
||||||
|
aapsLogger.info(LTag.GARMIN, "connect from $caller, req: $uri")
|
||||||
|
val minutes = getQueryParameter(uri, "disconnectMinutes", 0L).toInt()
|
||||||
|
if (minutes > 0) {
|
||||||
|
loopHub.disconnectPump(minutes)
|
||||||
|
} else {
|
||||||
|
loopHub.connectPump()
|
||||||
|
}
|
||||||
|
|
||||||
|
val jo = JsonObject()
|
||||||
|
jo.addProperty("connected", loopHub.isConnected)
|
||||||
|
return jo.toString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,9 +29,19 @@ interface LoopHub {
|
||||||
/** Returns the factor by which the basal rate is currently raised (> 1) or lowered (< 1). */
|
/** Returns the factor by which the basal rate is currently raised (> 1) or lowered (< 1). */
|
||||||
val temporaryBasal: Double
|
val temporaryBasal: Double
|
||||||
|
|
||||||
|
/** Tells the loop algorithm that the pump is physically connected. */
|
||||||
|
fun connectPump()
|
||||||
|
|
||||||
|
/** Tells the loop algorithm that the pump will be physically disconnected
|
||||||
|
* for the given number of minutes. */
|
||||||
|
fun disconnectPump(minutes: Int)
|
||||||
|
|
||||||
/** Retrieves the glucose values starting at from. */
|
/** Retrieves the glucose values starting at from. */
|
||||||
fun getGlucoseValues(from: Instant, ascending: Boolean): List<GlucoseValue>
|
fun getGlucoseValues(from: Instant, ascending: Boolean): List<GlucoseValue>
|
||||||
|
|
||||||
|
/** Notifies the system that carbs were eaten and stores the value. */
|
||||||
|
fun postCarbs(carbohydrates: Int)
|
||||||
|
|
||||||
/** Stores hear rate readings that a taken and averaged of the given interval. */
|
/** Stores hear rate readings that a taken and averaged of the given interval. */
|
||||||
fun storeHeartRate(
|
fun storeHeartRate(
|
||||||
samplingStart: Instant, samplingEnd: Instant,
|
samplingStart: Instant, samplingEnd: Instant,
|
||||||
|
|
|
@ -2,15 +2,26 @@ package app.aaps.plugins.sync.garmin
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import app.aaps.core.interfaces.aps.Loop
|
import app.aaps.core.interfaces.aps.Loop
|
||||||
|
import app.aaps.core.interfaces.constraints.ConstraintsChecker
|
||||||
import app.aaps.core.interfaces.db.GlucoseUnit
|
import app.aaps.core.interfaces.db.GlucoseUnit
|
||||||
import app.aaps.core.interfaces.iob.IobCobCalculator
|
import app.aaps.core.interfaces.iob.IobCobCalculator
|
||||||
|
import app.aaps.core.interfaces.logging.AAPSLogger
|
||||||
|
import app.aaps.core.interfaces.logging.LTag
|
||||||
|
import app.aaps.core.interfaces.logging.UserEntryLogger
|
||||||
import app.aaps.core.interfaces.profile.Profile
|
import app.aaps.core.interfaces.profile.Profile
|
||||||
import app.aaps.core.interfaces.profile.ProfileFunction
|
import app.aaps.core.interfaces.profile.ProfileFunction
|
||||||
|
import app.aaps.core.interfaces.pump.DetailedBolusInfo
|
||||||
|
import app.aaps.core.interfaces.queue.CommandQueue
|
||||||
|
import app.aaps.core.interfaces.sharedPreferences.SP
|
||||||
import app.aaps.database.ValueWrapper
|
import app.aaps.database.ValueWrapper
|
||||||
import app.aaps.database.entities.EffectiveProfileSwitch
|
import app.aaps.database.entities.EffectiveProfileSwitch
|
||||||
import app.aaps.database.entities.GlucoseValue
|
import app.aaps.database.entities.GlucoseValue
|
||||||
import app.aaps.database.entities.HeartRate
|
import app.aaps.database.entities.HeartRate
|
||||||
|
import app.aaps.database.entities.OfflineEvent
|
||||||
|
import app.aaps.database.entities.UserEntry
|
||||||
|
import app.aaps.database.entities.ValueWithUnit
|
||||||
import app.aaps.database.impl.AppRepository
|
import app.aaps.database.impl.AppRepository
|
||||||
|
import app.aaps.database.impl.transactions.CancelCurrentOfflineEventIfAnyTransaction
|
||||||
import app.aaps.database.impl.transactions.InsertOrUpdateHeartRateTransaction
|
import app.aaps.database.impl.transactions.InsertOrUpdateHeartRateTransaction
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
@ -22,10 +33,15 @@ import javax.inject.Singleton
|
||||||
* Interface to the functionality of the looping algorithm and storage systems.
|
* Interface to the functionality of the looping algorithm and storage systems.
|
||||||
*/
|
*/
|
||||||
class LoopHubImpl @Inject constructor(
|
class LoopHubImpl @Inject constructor(
|
||||||
|
private val aapsLogger: AAPSLogger,
|
||||||
|
private val commandQueue: CommandQueue,
|
||||||
|
private val constraintChecker: ConstraintsChecker,
|
||||||
private val iobCobCalculator: IobCobCalculator,
|
private val iobCobCalculator: IobCobCalculator,
|
||||||
private val loop: Loop,
|
private val loop: Loop,
|
||||||
private val profileFunction: ProfileFunction,
|
private val profileFunction: ProfileFunction,
|
||||||
private val repo: AppRepository,
|
private val repo: AppRepository,
|
||||||
|
private val userEntryLogger: UserEntryLogger,
|
||||||
|
private val sp: SP,
|
||||||
) : LoopHub {
|
) : LoopHub {
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
@ -40,7 +56,9 @@ class LoopHubImpl @Inject constructor(
|
||||||
|
|
||||||
/** Returns the glucose unit (mg/dl or mmol/l) as selected by the user. */
|
/** Returns the glucose unit (mg/dl or mmol/l) as selected by the user. */
|
||||||
override val glucoseUnit: GlucoseUnit
|
override val glucoseUnit: GlucoseUnit
|
||||||
get() = profileFunction.getProfile()?.units ?: GlucoseUnit.MGDL
|
get() = GlucoseUnit.fromText(sp.getString(
|
||||||
|
app.aaps.core.utils.R.string.key_units,
|
||||||
|
GlucoseUnit.MGDL.asText))
|
||||||
|
|
||||||
/** Returns the remaining bolus insulin on board. */
|
/** Returns the remaining bolus insulin on board. */
|
||||||
override val insulinOnboard: Double
|
override val insulinOnboard: Double
|
||||||
|
@ -65,12 +83,51 @@ class LoopHubImpl @Inject constructor(
|
||||||
return if (apsResult == null) Double.NaN else apsResult.percent / 100.0
|
return if (apsResult == null) Double.NaN else apsResult.percent / 100.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Tells the loop algorithm that the pump is physicallly connected. */
|
||||||
|
override fun connectPump() {
|
||||||
|
repo.runTransaction(
|
||||||
|
CancelCurrentOfflineEventIfAnyTransaction(clock.millis())
|
||||||
|
).subscribe()
|
||||||
|
commandQueue.cancelTempBasal(true, null)
|
||||||
|
userEntryLogger.log(UserEntry.Action.RECONNECT, UserEntry.Sources.GarminDevice)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tells the loop algorithm that the pump will be physically disconnected
|
||||||
|
* for the given number of minutes. */
|
||||||
|
override fun disconnectPump(minutes: Int) {
|
||||||
|
currentProfile?.let { p ->
|
||||||
|
loop.goToZeroTemp(minutes, p, OfflineEvent.Reason.DISCONNECT_PUMP)
|
||||||
|
userEntryLogger.log(
|
||||||
|
UserEntry.Action.DISCONNECT,
|
||||||
|
UserEntry.Sources.GarminDevice,
|
||||||
|
ValueWithUnit.Minute(minutes)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Retrieves the glucose values starting at from. */
|
/** Retrieves the glucose values starting at from. */
|
||||||
override fun getGlucoseValues(from: Instant, ascending: Boolean): List<GlucoseValue> {
|
override fun getGlucoseValues(from: Instant, ascending: Boolean): List<GlucoseValue> {
|
||||||
return repo.compatGetBgReadingsDataFromTime(from.toEpochMilli(), ascending)
|
return repo.compatGetBgReadingsDataFromTime(from.toEpochMilli(), ascending)
|
||||||
.blockingGet()
|
.blockingGet()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Notifies the system that carbs were eaten and stores the value. */
|
||||||
|
override fun postCarbs(carbohydrates: Int) {
|
||||||
|
aapsLogger.info(LTag.GARMIN, "post $carbohydrates g carbohydrates")
|
||||||
|
val carbsAfterConstraints =
|
||||||
|
carbohydrates.coerceAtMost(constraintChecker.getMaxCarbsAllowed().value())
|
||||||
|
userEntryLogger.log(
|
||||||
|
UserEntry.Action.CARBS,
|
||||||
|
UserEntry.Sources.GarminDevice,
|
||||||
|
ValueWithUnit.Gram(carbsAfterConstraints)
|
||||||
|
)
|
||||||
|
val detailedBolusInfo = DetailedBolusInfo().apply {
|
||||||
|
eventType = DetailedBolusInfo.EventType.CARBS_CORRECTION
|
||||||
|
carbs = carbsAfterConstraints.toDouble()
|
||||||
|
}
|
||||||
|
commandQueue.bolus(detailedBolusInfo, null)
|
||||||
|
}
|
||||||
|
|
||||||
/** Stores hear rate readings that a taken and averaged of the given interval. */
|
/** Stores hear rate readings that a taken and averaged of the given interval. */
|
||||||
override fun storeHeartRate(
|
override fun storeHeartRate(
|
||||||
samplingStart: Instant, samplingEnd: Instant,
|
samplingStart: Instant, samplingEnd: Instant,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package app.aaps.plugins.sync.garmin
|
package app.aaps.plugins.sync.garmin
|
||||||
|
|
||||||
|
import app.aaps.core.interfaces.db.GlucoseUnit
|
||||||
import app.aaps.core.interfaces.resources.ResourceHelper
|
import app.aaps.core.interfaces.resources.ResourceHelper
|
||||||
import app.aaps.core.interfaces.rx.events.EventNewBG
|
import app.aaps.core.interfaces.rx.events.EventNewBG
|
||||||
import app.aaps.core.interfaces.sharedPreferences.SP
|
import app.aaps.core.interfaces.sharedPreferences.SP
|
||||||
|
@ -9,14 +10,18 @@ import dagger.android.AndroidInjector
|
||||||
import dagger.android.HasAndroidInjector
|
import dagger.android.HasAndroidInjector
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Assertions.assertArrayEquals
|
import org.junit.jupiter.api.Assertions.assertArrayEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.ArgumentMatchers.anyLong
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.atMost
|
import org.mockito.Mockito.atMost
|
||||||
import org.mockito.Mockito.mock
|
import org.mockito.Mockito.mock
|
||||||
|
import org.mockito.Mockito.times
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.Mockito.verifyNoMoreInteractions
|
import org.mockito.Mockito.verifyNoMoreInteractions
|
||||||
import org.mockito.Mockito.`when`
|
import org.mockito.Mockito.`when`
|
||||||
|
import java.net.SocketAddress
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
@ -113,4 +118,91 @@ class GarminPluginTest: TestBase() {
|
||||||
verify(gp.newValue).signalAll()
|
verify(gp.newValue).signalAll()
|
||||||
verify(loopHub).getGlucoseValues(from, true)
|
verify(loopHub).getGlucoseValues(from, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnGetBloodGlucose() {
|
||||||
|
`when`(loopHub.isConnected).thenReturn(true)
|
||||||
|
`when`(loopHub.insulinOnboard).thenReturn(3.14)
|
||||||
|
`when`(loopHub.temporaryBasal).thenReturn(0.8)
|
||||||
|
val from = getGlucoseValuesFrom
|
||||||
|
`when`(loopHub.getGlucoseValues(from, true)).thenReturn(
|
||||||
|
listOf(createGlucoseValue(Instant.ofEpochSecond(1_000))))
|
||||||
|
val hr = createHeartRate(99)
|
||||||
|
val uri = createUri(hr)
|
||||||
|
val result = gp.onGetBloodGlucose(mock(SocketAddress::class.java), uri, null)
|
||||||
|
assertEquals(
|
||||||
|
"{\"encodedGlucose\":\"0A+6AQ==\"," +
|
||||||
|
"\"remainingInsulin\":3.14," +
|
||||||
|
"\"glucoseUnit\":\"mmoll\",\"temporaryBasalRate\":0.8," +
|
||||||
|
"\"profile\":\"D\",\"connected\":true}",
|
||||||
|
result.toString())
|
||||||
|
verify(loopHub).getGlucoseValues(from, true)
|
||||||
|
verify(loopHub).insulinOnboard
|
||||||
|
verify(loopHub).temporaryBasal
|
||||||
|
verify(loopHub).isConnected
|
||||||
|
verify(loopHub).glucoseUnit
|
||||||
|
verify(loopHub).storeHeartRate(
|
||||||
|
Instant.ofEpochSecond(hr["hrStart"] as Long),
|
||||||
|
Instant.ofEpochSecond(hr["hrEnd"] as Long),
|
||||||
|
99,
|
||||||
|
hr["device"] as String)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnGetBloodGlucose_Wait() {
|
||||||
|
`when`(loopHub.isConnected).thenReturn(true)
|
||||||
|
`when`(loopHub.insulinOnboard).thenReturn(3.14)
|
||||||
|
`when`(loopHub.temporaryBasal).thenReturn(0.8)
|
||||||
|
`when`(loopHub.glucoseUnit).thenReturn(GlucoseUnit.MMOL)
|
||||||
|
val from = getGlucoseValuesFrom
|
||||||
|
`when`(loopHub.getGlucoseValues(from, true)).thenReturn(
|
||||||
|
listOf(createGlucoseValue(clock.instant().minusSeconds(330))))
|
||||||
|
val params = createHeartRate(99).toMutableMap()
|
||||||
|
params["wait"] = 10
|
||||||
|
val uri = createUri(params)
|
||||||
|
gp.newValue = mock(Condition::class.java)
|
||||||
|
val result = gp.onGetBloodGlucose(mock(SocketAddress::class.java), uri, null)
|
||||||
|
assertEquals(
|
||||||
|
"{\"encodedGlucose\":\"/wS6AQ==\"," +
|
||||||
|
"\"remainingInsulin\":3.14," +
|
||||||
|
"\"glucoseUnit\":\"mmoll\",\"temporaryBasalRate\":0.8," +
|
||||||
|
"\"profile\":\"D\",\"connected\":true}",
|
||||||
|
result.toString())
|
||||||
|
verify(gp.newValue).awaitNanos(anyLong())
|
||||||
|
verify(loopHub, times(2)).getGlucoseValues(from, true)
|
||||||
|
verify(loopHub).insulinOnboard
|
||||||
|
verify(loopHub).temporaryBasal
|
||||||
|
verify(loopHub).isConnected
|
||||||
|
verify(loopHub).glucoseUnit
|
||||||
|
verify(loopHub).storeHeartRate(
|
||||||
|
Instant.ofEpochSecond(params["hrStart"] as Long),
|
||||||
|
Instant.ofEpochSecond(params["hrEnd"] as Long),
|
||||||
|
99,
|
||||||
|
params["device"] as String)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnPostCarbs() {
|
||||||
|
val uri = createUri(mapOf("carbs" to "12"))
|
||||||
|
assertEquals("", gp.onPostCarbs(mock(SocketAddress::class.java), uri, null))
|
||||||
|
verify(loopHub).postCarbs(12)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnConnectPump_Disconnect() {
|
||||||
|
val uri = createUri(mapOf("disconnectMinutes" to "20"))
|
||||||
|
`when`(loopHub.isConnected).thenReturn(false)
|
||||||
|
assertEquals("{\"connected\":false}", gp.onConnectPump(mock(SocketAddress::class.java), uri, null))
|
||||||
|
verify(loopHub).disconnectPump(20)
|
||||||
|
verify(loopHub).isConnected
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnConnectPump_Connect() {
|
||||||
|
val uri = createUri(mapOf("disconnectMinutes" to "0"))
|
||||||
|
`when`(loopHub.isConnected).thenReturn(true)
|
||||||
|
assertEquals("{\"connected\":true}", gp.onConnectPump(mock(SocketAddress::class.java), uri, null))
|
||||||
|
verify(loopHub).connectPump()
|
||||||
|
verify(loopHub).isConnected
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package app.aaps.plugins.sync.garmin
|
package app.aaps.plugins.sync.garmin
|
||||||
|
|
||||||
|
|
||||||
import app.aaps.core.interfaces.aps.APSResult
|
import app.aaps.core.interfaces.aps.APSResult
|
||||||
import app.aaps.core.interfaces.aps.Loop
|
import app.aaps.core.interfaces.aps.Loop
|
||||||
|
import app.aaps.core.interfaces.constraints.Constraint
|
||||||
import app.aaps.core.interfaces.constraints.ConstraintsChecker
|
import app.aaps.core.interfaces.constraints.ConstraintsChecker
|
||||||
import app.aaps.core.interfaces.db.GlucoseUnit
|
import app.aaps.core.interfaces.db.GlucoseUnit
|
||||||
import app.aaps.core.interfaces.iob.IobCobCalculator
|
import app.aaps.core.interfaces.iob.IobCobCalculator
|
||||||
|
@ -10,13 +10,19 @@ import app.aaps.core.interfaces.iob.IobTotal
|
||||||
import app.aaps.core.interfaces.logging.UserEntryLogger
|
import app.aaps.core.interfaces.logging.UserEntryLogger
|
||||||
import app.aaps.core.interfaces.profile.Profile
|
import app.aaps.core.interfaces.profile.Profile
|
||||||
import app.aaps.core.interfaces.profile.ProfileFunction
|
import app.aaps.core.interfaces.profile.ProfileFunction
|
||||||
|
import app.aaps.core.interfaces.pump.DetailedBolusInfo
|
||||||
import app.aaps.core.interfaces.queue.CommandQueue
|
import app.aaps.core.interfaces.queue.CommandQueue
|
||||||
|
import app.aaps.core.interfaces.sharedPreferences.SP
|
||||||
import app.aaps.database.ValueWrapper
|
import app.aaps.database.ValueWrapper
|
||||||
import app.aaps.database.entities.EffectiveProfileSwitch
|
import app.aaps.database.entities.EffectiveProfileSwitch
|
||||||
import app.aaps.database.entities.GlucoseValue
|
import app.aaps.database.entities.GlucoseValue
|
||||||
import app.aaps.database.entities.HeartRate
|
import app.aaps.database.entities.HeartRate
|
||||||
|
import app.aaps.database.entities.OfflineEvent
|
||||||
|
import app.aaps.database.entities.UserEntry
|
||||||
|
import app.aaps.database.entities.ValueWithUnit
|
||||||
import app.aaps.database.entities.embedments.InsulinConfiguration
|
import app.aaps.database.entities.embedments.InsulinConfiguration
|
||||||
import app.aaps.database.impl.AppRepository
|
import app.aaps.database.impl.AppRepository
|
||||||
|
import app.aaps.database.impl.transactions.CancelCurrentOfflineEventIfAnyTransaction
|
||||||
import app.aaps.database.impl.transactions.InsertOrUpdateHeartRateTransaction
|
import app.aaps.database.impl.transactions.InsertOrUpdateHeartRateTransaction
|
||||||
import app.aaps.shared.tests.TestBase
|
import app.aaps.shared.tests.TestBase
|
||||||
import io.reactivex.rxjava3.core.Completable
|
import io.reactivex.rxjava3.core.Completable
|
||||||
|
@ -27,6 +33,8 @@ import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.ArgumentMatchers.argThat
|
||||||
|
import org.mockito.ArgumentMatchers.isNull
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.mock
|
import org.mockito.Mockito.mock
|
||||||
import org.mockito.Mockito.times
|
import org.mockito.Mockito.times
|
||||||
|
@ -45,13 +53,17 @@ class LoopHubTest: TestBase() {
|
||||||
@Mock lateinit var profileFunction: ProfileFunction
|
@Mock lateinit var profileFunction: ProfileFunction
|
||||||
@Mock lateinit var repo: AppRepository
|
@Mock lateinit var repo: AppRepository
|
||||||
@Mock lateinit var userEntryLogger: UserEntryLogger
|
@Mock lateinit var userEntryLogger: UserEntryLogger
|
||||||
|
@Mock lateinit var sp: SP
|
||||||
|
|
||||||
private lateinit var loopHub: LoopHubImpl
|
private lateinit var loopHub: LoopHubImpl
|
||||||
private val clock = Clock.fixed(Instant.ofEpochMilli(10_000), ZoneId.of("UTC"))
|
private val clock = Clock.fixed(Instant.ofEpochMilli(10_000), ZoneId.of("UTC"))
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup() {
|
fun setup() {
|
||||||
loopHub = LoopHubImpl(iobCobCalculator, loop, profileFunction, repo)
|
loopHub = LoopHubImpl(
|
||||||
|
aapsLogger, commandQueue, constraints, iobCobCalculator, loop,
|
||||||
|
profileFunction, repo, userEntryLogger, sp
|
||||||
|
)
|
||||||
loopHub.clock = clock
|
loopHub.clock = clock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,18 +95,10 @@ class LoopHubTest: TestBase() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGlucoseUnit() {
|
fun testGlucoseUnit() {
|
||||||
val profile = mock(Profile::class.java)
|
`when`(sp.getString(app.aaps.core.utils.R.string.key_units, GlucoseUnit.MGDL.asText)).thenReturn("mg/dl")
|
||||||
`when`(profile.units).thenReturn(GlucoseUnit.MMOL)
|
|
||||||
`when`(profileFunction.getProfile()).thenReturn(profile)
|
|
||||||
assertEquals(GlucoseUnit.MMOL, loopHub.glucoseUnit)
|
|
||||||
verify(profileFunction, times(1)).getProfile()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGlucoseUnitNullProfile() {
|
|
||||||
`when`(profileFunction.getProfile()).thenReturn(null)
|
|
||||||
assertEquals(GlucoseUnit.MGDL, loopHub.glucoseUnit)
|
assertEquals(GlucoseUnit.MGDL, loopHub.glucoseUnit)
|
||||||
verify(profileFunction, times(1)).getProfile()
|
`when`(sp.getString(app.aaps.core.utils.R.string.key_units, GlucoseUnit.MGDL.asText)).thenReturn("mmol")
|
||||||
|
assertEquals(GlucoseUnit.MMOL, loopHub.glucoseUnit)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -165,6 +169,32 @@ class LoopHubTest: TestBase() {
|
||||||
verify(loop, times(1)).lastRun
|
verify(loop, times(1)).lastRun
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testConnectPump() {
|
||||||
|
val c = mock(Completable::class.java)
|
||||||
|
val dummy = CancelCurrentOfflineEventIfAnyTransaction(0)
|
||||||
|
val matcher = {
|
||||||
|
argThat<CancelCurrentOfflineEventIfAnyTransaction> { t -> t.timestamp == clock.millis() }}
|
||||||
|
`when`(repo.runTransaction(matcher() ?: dummy)).thenReturn(c)
|
||||||
|
loopHub.connectPump()
|
||||||
|
verify(repo).runTransaction(matcher() ?: dummy)
|
||||||
|
verify(commandQueue).cancelTempBasal(true, null)
|
||||||
|
verify(userEntryLogger).log(UserEntry.Action.RECONNECT, UserEntry.Sources.GarminDevice)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDisconnectPump() {
|
||||||
|
val profile = mock(Profile::class.java)
|
||||||
|
`when`(profileFunction.getProfile()).thenReturn(profile)
|
||||||
|
loopHub.disconnectPump(23)
|
||||||
|
verify(profileFunction).getProfile()
|
||||||
|
verify(loop).goToZeroTemp(23, profile, OfflineEvent.Reason.DISCONNECT_PUMP)
|
||||||
|
verify(userEntryLogger).log(
|
||||||
|
UserEntry.Action.DISCONNECT,
|
||||||
|
UserEntry.Sources.GarminDevice,
|
||||||
|
ValueWithUnit.Minute(23))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGetGlucoseValues() {
|
fun testGetGlucoseValues() {
|
||||||
val glucoseValues = listOf(
|
val glucoseValues = listOf(
|
||||||
|
@ -180,6 +210,25 @@ class LoopHubTest: TestBase() {
|
||||||
verify(repo).compatGetBgReadingsDataFromTime(1001_000, false)
|
verify(repo).compatGetBgReadingsDataFromTime(1001_000, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPostCarbs() {
|
||||||
|
@Suppress("unchecked_cast")
|
||||||
|
val constraint = mock(Constraint::class.java) as Constraint<Int>
|
||||||
|
`when`(constraint.value()).thenReturn(99)
|
||||||
|
`when`(constraints.getMaxCarbsAllowed()).thenReturn(constraint)
|
||||||
|
loopHub.postCarbs(100)
|
||||||
|
verify(constraints).getMaxCarbsAllowed()
|
||||||
|
verify(userEntryLogger).log(
|
||||||
|
UserEntry.Action.CARBS,
|
||||||
|
UserEntry.Sources.GarminDevice,
|
||||||
|
ValueWithUnit.Gram(99))
|
||||||
|
verify(commandQueue).bolus(
|
||||||
|
argThat { b ->
|
||||||
|
b!!.eventType == DetailedBolusInfo.EventType.CARBS_CORRECTION &&
|
||||||
|
b.carbs == 99.0 }?: DetailedBolusInfo() ,
|
||||||
|
isNull())
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testStoreHeartRate() {
|
fun testStoreHeartRate() {
|
||||||
val samplingStart = Instant.ofEpochMilli(1_001_000)
|
val samplingStart = Instant.ofEpochMilli(1_001_000)
|
||||||
|
|
Loading…
Reference in a new issue