Merge pull request #3082 from swissalpine/garmin-sgv-json

Add iob, cob, tbr to GARMIN sgv.json endpoint
This commit is contained in:
Milos Kozak 2023-12-01 16:47:02 +01:00 committed by GitHub
commit 3cdf73ce46
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 69 additions and 12 deletions

View file

@ -327,6 +327,14 @@ class GarminPlugin @Inject constructor(
GlucoseUnit.MGDL -> jo.addProperty("units_hint", "mgdl") GlucoseUnit.MGDL -> jo.addProperty("units_hint", "mgdl")
GlucoseUnit.MMOL -> jo.addProperty("units_hint", "mmol") GlucoseUnit.MMOL -> jo.addProperty("units_hint", "mmol")
} }
jo.addProperty("iob", loopHub.insulinOnboard + loopHub.insulinBasalOnboard)
loopHub.temporaryBasal.also {
if (!it.isNaN()) {
val temporaryBasalRateInPercent = (it * 100.0).toInt()
jo.addProperty("tbr", temporaryBasalRateInPercent)
}
}
jo.addProperty("cob", loopHub.carbsOnboard)
} }
joa.add(jo) joa.add(jo)
} }

View file

@ -20,6 +20,12 @@ interface LoopHub {
/** Returns the remaining bolus insulin on board. */ /** Returns the remaining bolus insulin on board. */
val insulinOnboard: Double val insulinOnboard: Double
/** Returns the basal insulin on board. */
val insulinBasalOnboard: Double
/** Returns the remaining carbs on board. */
val carbsOnboard: Double?
/** Returns true if the pump is connected. */ /** Returns true if the pump is connected. */
val isConnected: Boolean val isConnected: Boolean
@ -48,4 +54,4 @@ interface LoopHub {
avgHeartRate: Int, avgHeartRate: Int,
device: String? device: String?
) )
} }

View file

@ -13,6 +13,7 @@ import app.aaps.core.interfaces.profile.ProfileFunction
import app.aaps.core.interfaces.pump.DetailedBolusInfo 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.core.interfaces.sharedPreferences.SP
import app.aaps.core.main.graph.OverviewData
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
@ -42,6 +43,7 @@ class LoopHubImpl @Inject constructor(
private val repo: AppRepository, private val repo: AppRepository,
private val userEntryLogger: UserEntryLogger, private val userEntryLogger: UserEntryLogger,
private val sp: SP, private val sp: SP,
private val overviewData: OverviewData,
) : LoopHub { ) : LoopHub {
@VisibleForTesting @VisibleForTesting
@ -64,6 +66,14 @@ class LoopHubImpl @Inject constructor(
override val insulinOnboard: Double override val insulinOnboard: Double
get() = iobCobCalculator.calculateIobFromBolus().iob get() = iobCobCalculator.calculateIobFromBolus().iob
/** Returns the remaining bolus and basal insulin on board. */
override val insulinBasalOnboard :Double
get() = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().basaliob
/** Returns the remaining carbs on board. */
override val carbsOnboard: Double?
get() = overviewData.cobInfo(iobCobCalculator).displayCob
/** Returns true if the pump is connected. */ /** Returns true if the pump is connected. */
override val isConnected: Boolean get() = !loop.isDisconnected override val isConnected: Boolean get() = !loop.isDisconnected
@ -142,4 +152,4 @@ class LoopHubImpl @Inject constructor(
) )
repo.runTransaction(InsertOrUpdateHeartRateTransaction(hr)).blockingAwait() repo.runTransaction(InsertOrUpdateHeartRateTransaction(hr)).blockingAwait()
} }
} }

View file

@ -57,6 +57,10 @@ class GarminPluginTest: TestBase() {
@AfterEach @AfterEach
fun verifyNoFurtherInteractions() { fun verifyNoFurtherInteractions() {
verify(loopHub, atMost(2)).currentProfileName verify(loopHub, atMost(2)).currentProfileName
verify(loopHub, atMost(3)).insulinOnboard
verify(loopHub, atMost(3)).insulinBasalOnboard
verify(loopHub, atMost(3)).temporaryBasal
verify(loopHub, atMost(3)).carbsOnboard
verifyNoMoreInteractions(loopHub) verifyNoMoreInteractions(loopHub)
} }
@ -221,14 +225,18 @@ class GarminPluginTest: TestBase() {
} }
@Test @Test
fun onSgv_NoDelta() { fun onSgv_NoDelta() {
whenever(loopHub.glucoseUnit).thenReturn(GlucoseUnit.MMOL) whenever(loopHub.glucoseUnit).thenReturn(GlucoseUnit.MMOL)
whenever(loopHub.insulinOnboard).thenReturn(2.7)
whenever(loopHub.insulinBasalOnboard).thenReturn(2.5)
whenever(loopHub.temporaryBasal).thenReturn(0.8)
whenever(loopHub.carbsOnboard).thenReturn(10.7)
whenever(loopHub.getGlucoseValues(any(), eq(false))).thenReturn( whenever(loopHub.getGlucoseValues(any(), eq(false))).thenReturn(
listOf(createGlucoseValue( listOf(createGlucoseValue(
clock.instant().minusSeconds(100L), 99.3))) clock.instant().minusSeconds(100L), 99.3)))
assertEquals( assertEquals(
"""[{"_id":"-900000","device":"RANDOM","deviceString":"1969-12-31T23:58:30Z","sysTime":"1969-12-31T23:58:30Z","unfiltered":90.0,"date":-90000,"sgv":99,"direction":"Flat","noise":4.5,"units_hint":"mmol"}]""", """[{"_id":"-900000","device":"RANDOM","deviceString":"1969-12-31T23:58:30Z","sysTime":"1969-12-31T23:58:30Z","unfiltered":90.0,"date":-90000,"sgv":99,"direction":"Flat","noise":4.5,"units_hint":"mmol","iob":5.2,"tbr":80,"cob":10.7}]""",
gp.onSgv(mock(), createUri(mapOf()), null)) gp.onSgv(mock(), createUri(mapOf()), null))
verify(loopHub).getGlucoseValues(clock.instant().minusSeconds(25L * 300L), false) verify(loopHub).getGlucoseValues(clock.instant().minusSeconds(25L * 300L), false)
verify(loopHub).glucoseUnit verify(loopHub).glucoseUnit
} }
@ -236,26 +244,31 @@ class GarminPluginTest: TestBase() {
@Test @Test
fun onSgv() { fun onSgv() {
whenever(loopHub.glucoseUnit).thenReturn(GlucoseUnit.MMOL) whenever(loopHub.glucoseUnit).thenReturn(GlucoseUnit.MMOL)
whenever(loopHub.insulinOnboard).thenReturn(2.7)
whenever(loopHub.insulinBasalOnboard).thenReturn(2.5)
whenever(loopHub.temporaryBasal).thenReturn(0.8)
whenever(loopHub.carbsOnboard).thenReturn(10.7)
whenever(loopHub.getGlucoseValues(any(), eq(false))).thenAnswer { i -> whenever(loopHub.getGlucoseValues(any(), eq(false))).thenAnswer { i ->
val from = i.getArgument<Instant>(0) val from = i.getArgument<Instant>(0)
fromClosedRange(from.toEpochMilli(), clock.instant().toEpochMilli(), 300_000L) fromClosedRange(from.toEpochMilli(), clock.instant().toEpochMilli(), 300_000L)
.map(Instant::ofEpochMilli) .map(Instant::ofEpochMilli)
.mapIndexed { idx, ts -> createGlucoseValue(ts, 100.0+(10 * idx)) }.reversed()} .mapIndexed { idx, ts -> createGlucoseValue(ts, 100.0+(10 * idx)) }.reversed()}
assertEquals( assertEquals(
"""[{"_id":"100000","device":"RANDOM","deviceString":"1970-01-01T00:00:10Z","sysTime":"1970-01-01T00:00:10Z","unfiltered":90.0,"date":10000,"sgv":120,"delta":10,"direction":"Flat","noise":4.5,"units_hint":"mmol"}]""", """[{"_id":"100000","device":"RANDOM","deviceString":"1970-01-01T00:00:10Z","sysTime":"1970-01-01T00:00:10Z","unfiltered":90.0,"date":10000,"sgv":120,"delta":10,"direction":"Flat","noise":4.5,"units_hint":"mmol","iob":5.2,"tbr":80,"cob":10.7}]""",
gp.onSgv(mock(), createUri(mapOf("count" to "1")), null)) gp.onSgv(mock(), createUri(mapOf("count" to "1")), null))
verify(loopHub).getGlucoseValues( verify(loopHub).getGlucoseValues(
clock.instant().minusSeconds(600L), false) clock.instant().minusSeconds(600L), false)
assertEquals( assertEquals(
"""[{"_id":"100000","device":"RANDOM","deviceString":"1970-01-01T00:00:10Z","sysTime":"1970-01-01T00:00:10Z","unfiltered":90.0,"date":10000,"sgv":130,"delta":10,"direction":"Flat","noise":4.5,"units_hint":"mmol"},""" + """[{"_id":"100000","device":"RANDOM","deviceString":"1970-01-01T00:00:10Z","sysTime":"1970-01-01T00:00:10Z","unfiltered":90.0,"date":10000,"sgv":130,"delta":10,"direction":"Flat","noise":4.5,"units_hint":"mmol","iob":5.2,"tbr":80,"cob":10.7},""" +
"""{"_id":"-2900000","device":"RANDOM","deviceString":"1969-12-31T23:55:10Z","sysTime":"1969-12-31T23:55:10Z","unfiltered":90.0,"date":-290000,"sgv":120,"delta":10,"direction":"Flat","noise":4.5}]""", """{"_id":"-2900000","device":"RANDOM","deviceString":"1969-12-31T23:55:10Z","sysTime":"1969-12-31T23:55:10Z","unfiltered":90.0,"date":-290000,"sgv":120,"delta":10,"direction":"Flat","noise":4.5}]""",
gp.onSgv(mock(), createUri(mapOf("count" to "2")), null)) gp.onSgv(mock(), createUri(mapOf("count" to "2")), null))
verify(loopHub).getGlucoseValues( verify(loopHub).getGlucoseValues(
clock.instant().minusSeconds(900L), false) clock.instant().minusSeconds(900L), false)
assertEquals( assertEquals(
"""[{"date":10000,"sgv":130,"delta":10,"direction":"Flat","noise":4.5,"units_hint":"mmol"},""" + """[{"date":10000,"sgv":130,"delta":10,"direction":"Flat","noise":4.5,"units_hint":"mmol","iob":5.2,"tbr":80,"cob":10.7},""" +
"""{"date":-290000,"sgv":120,"delta":10,"direction":"Flat","noise":4.5}]""", """{"date":-290000,"sgv":120,"delta":10,"direction":"Flat","noise":4.5}]""",
gp.onSgv(mock(), createUri(mapOf("count" to "2", "brief_mode" to "true")), null)) gp.onSgv(mock(), createUri(mapOf("count" to "2", "brief_mode" to "true")), null))
verify(loopHub, times(2)).getGlucoseValues( verify(loopHub, times(2)).getGlucoseValues(

View file

@ -5,6 +5,7 @@ import app.aaps.core.interfaces.aps.Loop
import app.aaps.core.interfaces.constraints.Constraint 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.CobInfo
import app.aaps.core.interfaces.iob.IobCobCalculator import app.aaps.core.interfaces.iob.IobCobCalculator
import app.aaps.core.interfaces.iob.IobTotal import app.aaps.core.interfaces.iob.IobTotal
import app.aaps.core.interfaces.logging.UserEntryLogger import app.aaps.core.interfaces.logging.UserEntryLogger
@ -13,6 +14,7 @@ import app.aaps.core.interfaces.profile.ProfileFunction
import app.aaps.core.interfaces.pump.DetailedBolusInfo 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.core.interfaces.sharedPreferences.SP
import app.aaps.core.main.graph.OverviewData
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
@ -54,6 +56,7 @@ class LoopHubTest: TestBase() {
@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 @Mock lateinit var sp: SP
@Mock lateinit var overviewData: OverviewData
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"))
@ -62,7 +65,7 @@ class LoopHubTest: TestBase() {
fun setup() { fun setup() {
loopHub = LoopHubImpl( loopHub = LoopHubImpl(
aapsLogger, commandQueue, constraints, iobCobCalculator, loop, aapsLogger, commandQueue, constraints, iobCobCalculator, loop,
profileFunction, repo, userEntryLogger, sp profileFunction, repo, userEntryLogger, sp, overviewData
) )
loopHub.clock = clock loopHub.clock = clock
} }
@ -76,9 +79,10 @@ class LoopHubTest: TestBase() {
verifyNoMoreInteractions(profileFunction) verifyNoMoreInteractions(profileFunction)
verifyNoMoreInteractions(repo) verifyNoMoreInteractions(repo)
verifyNoMoreInteractions(userEntryLogger) verifyNoMoreInteractions(userEntryLogger)
verifyNoMoreInteractions(overviewData)
} }
@Test @Test
fun testCurrentProfile() { fun testCurrentProfile() {
val profile = mock(Profile::class.java) val profile = mock(Profile::class.java)
`when`(profileFunction.getProfile()).thenReturn(profile) `when`(profileFunction.getProfile()).thenReturn(profile)
@ -109,6 +113,22 @@ class LoopHubTest: TestBase() {
verify(iobCobCalculator, times(1)).calculateIobFromBolus() verify(iobCobCalculator, times(1)).calculateIobFromBolus()
} }
@Test
fun testBasalOnBoard() {
val iobBasal = IobTotal(time = 0).apply { basaliob = 23.9 }
`when`(iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended()).thenReturn(iobBasal)
assertEquals(23.9, loopHub.insulinBasalOnboard, 1e-10)
verify(iobCobCalculator, times(1)).calculateIobFromTempBasalsIncludingConvertedExtended()
}
@Test
fun testCarbsOnBoard() {
val cobInfo = CobInfo(0, 12.0, 0.0)
`when`(overviewData.cobInfo(iobCobCalculator)).thenReturn(cobInfo)
assertEquals(12.0, loopHub.carbsOnboard)
verify(overviewData, times(1)).cobInfo(iobCobCalculator)
}
@Test @Test
fun testIsConnected() { fun testIsConnected() {
`when`(loop.isDisconnected).thenReturn(false) `when`(loop.isDisconnected).thenReturn(false)
@ -247,4 +267,4 @@ class LoopHubTest: TestBase() {
samplingStart, samplingEnd, 101, "Test Device") samplingStart, samplingEnd, 101, "Test Device")
verify(repo).runTransaction(InsertOrUpdateHeartRateTransaction(hr)) verify(repo).runTransaction(InsertOrUpdateHeartRateTransaction(hr))
} }
} }