Merge remote-tracking branch 'origin/dev' into feature/new-sms-command

# Conflicts:
#	app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintsCheckerTest.kt
#	plugins/main/src/test/java/info/nightscout/plugins/general/smsCommunicator/SmsCommunicatorPluginTest.kt
This commit is contained in:
piotrek 2022-12-08 15:45:24 +01:00
commit 812499ea81
124 changed files with 3085 additions and 1400 deletions

View file

@ -151,10 +151,6 @@ class MainActivity : DaggerAppCompatActivityWithResult() {
.toObservable(EventPreferenceChange::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ processPreferenceChange(it) }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventInitializationChanged::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ passwordResetCheck(this) }, fabricPrivacy::logException)
if (startWizard() && !isRunningRealPumpTest()) {
protectionCheck.queryProtection(this, ProtectionCheck.Protection.PREFERENCES, {
startActivity(Intent(this, SetupWizardActivity::class.java))
@ -168,6 +164,7 @@ class MainActivity : DaggerAppCompatActivityWithResult() {
androidPermission.notifyForSystemWindowPermissions(this)
androidPermission.notifyForBtConnectPermission(this)
}
passwordResetCheck(this)
}
private fun checkPluginPreferences(viewPager: ViewPager2) {

View file

@ -10,10 +10,11 @@ import info.nightscout.androidaps.insight.database.InsightDatabase
import info.nightscout.androidaps.insight.database.InsightDatabaseDao
import info.nightscout.androidaps.insight.database.InsightDbHelper
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.database.impl.AppRepository
import info.nightscout.implementation.constraints.ConstraintsImpl
import info.nightscout.interfaces.ApsMode
import info.nightscout.implementation.iob.GlucoseStatusProviderImpl
import info.nightscout.interfaces.bgQualityCheck.BgQualityCheck
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.constraints.Objectives
@ -42,9 +43,10 @@ import info.nightscout.plugins.source.GlimpPlugin
import info.nightscout.pump.combo.ComboPlugin
import info.nightscout.pump.combo.ruffyscripter.RuffyScripter
import info.nightscout.pump.dana.DanaPump
import info.nightscout.pump.dana.R
import info.nightscout.pump.dana.database.DanaHistoryDatabase
import info.nightscout.shared.sharedPreferences.SP
import org.junit.Assert
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mock
@ -72,6 +74,7 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
@Mock lateinit var profileInstantiator: ProfileInstantiator
@Mock lateinit var danaHistoryDatabase: DanaHistoryDatabase
@Mock lateinit var insightDatabase: InsightDatabase
@Mock lateinit var bgQualityCheck: BgQualityCheck
private lateinit var hardLimits: HardLimits
private lateinit var danaPump: DanaPump
@ -130,13 +133,16 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
`when`(rh.gs(info.nightscout.plugins.R.string.objectivenotstarted)).thenReturn("Objective %1\$d not started")
// RS constructor
`when`(sp.getString(info.nightscout.pump.dana.R.string.key_danars_address, "")).thenReturn("")
`when`(sp.getString(R.string.key_danars_name, "")).thenReturn("")
`when`(sp.getString(R.string.key_danars_address, "")).thenReturn("")
// R
`when`(sp.getString(R.string.key_danar_bt_name, "")).thenReturn("")
//SafetyPlugin
`when`(activePlugin.activePump).thenReturn(virtualPumpPlugin)
constraintChecker = ConstraintsImpl(activePlugin)
val glucoseStatusProvider = GlucoseStatusProvider(aapsLogger = aapsLogger, iobCobCalculator = iobCobCalculator, dateUtil = dateUtil)
val glucoseStatusProvider = GlucoseStatusProviderImpl(aapsLogger = aapsLogger, iobCobCalculator = iobCobCalculator, dateUtil = dateUtil)
hardLimits = HardLimitsMock(sp, rh)
insightDbHelper = InsightDbHelper(insightDatabaseDao)
@ -183,7 +189,8 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
sp,
dateUtil,
repository,
glucoseStatusProvider
glucoseStatusProvider,
bgQualityCheck
)
openAPSSMBDynamicISFPlugin =
OpenAPSSMBDynamicISFPlugin(
@ -202,7 +209,8 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
dateUtil,
repository,
glucoseStatusProvider,
config
config,
bgQualityCheck
)
openAPSAMAPlugin =
OpenAPSAMAPlugin(
@ -257,9 +265,9 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
comboPlugin.setPluginEnabled(PluginType.PUMP, true)
comboPlugin.setValidBasalRateProfileSelectedOnPump(false)
val c = constraintChecker.isLoopInvocationAllowed()
Assert.assertEquals(true, c.reasonList.size == 2) // Combo & Objectives
Assert.assertEquals(true, c.mostLimitedReasonList.size == 2) // Combo & Objectives
Assert.assertEquals(java.lang.Boolean.FALSE, c.value())
Assertions.assertEquals(true, c.reasonList.size == 2) // Combo & Objectives
Assertions.assertEquals(true, c.mostLimitedReasonList.size == 2) // Combo & Objectives
Assertions.assertEquals(java.lang.Boolean.FALSE, c.value())
}
// Safety & Objectives
@ -270,13 +278,13 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
objectivesPlugin.objectives[Objectives.MAXIOB_ZERO_CL_OBJECTIVE].startedOn = 0
var c: Constraint<Boolean> = constraintChecker.isClosedLoopAllowed()
aapsLogger.debug("Reason list: " + c.reasonList.toString())
// Assert.assertTrue(c.reasonList[0].toString().contains("Closed loop is disabled")) // Safety & Objectives
Assert.assertEquals(false, c.value())
// Assertions.assertTrue(c.reasonList[0].toString().contains("Closed loop is disabled")) // Safety & Objectives
Assertions.assertEquals(false, c.value())
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, "open")).thenReturn("open")
c = constraintChecker.isClosedLoopAllowed()
Assert.assertTrue(c.reasonList[0].contains("Closed loop mode disabled in preferences")) // Safety & Objectives
// Assert.assertEquals(3, c.reasonList.size) // 2x Safety & Objectives
Assert.assertEquals(false, c.value())
Assertions.assertTrue(c.reasonList[0].contains("Closed loop mode disabled in preferences")) // Safety & Objectives
// Assertions.assertEquals(3, c.reasonList.size) // 2x Safety & Objectives
Assertions.assertEquals(false, c.value())
}
// Safety & Objectives
@ -286,9 +294,9 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
objectivesPlugin.objectives[Objectives.AUTOSENS_OBJECTIVE].startedOn = 0
`when`(sp.getBoolean(info.nightscout.plugins.aps.R.string.key_openapsama_use_autosens, false)).thenReturn(false)
val c = constraintChecker.isAutosensModeEnabled()
Assert.assertEquals(true, c.reasonList.size == 2) // Safety & Objectives
Assert.assertEquals(true, c.mostLimitedReasonList.size == 2) // Safety & Objectives
Assert.assertEquals(java.lang.Boolean.FALSE, c.value())
Assertions.assertEquals(true, c.reasonList.size == 2) // Safety & Objectives
Assertions.assertEquals(true, c.mostLimitedReasonList.size == 2) // Safety & Objectives
Assertions.assertEquals(java.lang.Boolean.FALSE, c.value())
}
// Safety
@ -296,9 +304,9 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
fun isAdvancedFilteringEnabledTest() {
`when`(activePlugin.activeBgSource).thenReturn(glimpPlugin)
val c = constraintChecker.isAdvancedFilteringEnabled()
Assert.assertEquals(true, c.reasonList.size == 1) // Safety
Assert.assertEquals(true, c.mostLimitedReasonList.size == 1) // Safety
Assert.assertEquals(false, c.value())
Assertions.assertEquals(true, c.reasonList.size == 1) // Safety
Assertions.assertEquals(true, c.mostLimitedReasonList.size == 1) // Safety
Assertions.assertEquals(false, c.value())
}
// SMB should limit
@ -306,7 +314,7 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
fun isSuperBolusEnabledTest() {
openAPSSMBPlugin.setPluginEnabled(PluginType.APS, true)
val c = constraintChecker.isSuperBolusEnabled()
Assert.assertEquals(java.lang.Boolean.FALSE, c.value()) // SMB should limit
Assertions.assertEquals(java.lang.Boolean.FALSE, c.value()) // SMB should limit
}
// Safety & Objectives
@ -318,9 +326,9 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, "open")).thenReturn("open")
// `when`(constraintChecker.isClosedLoopAllowed()).thenReturn(Constraint(true))
val c = constraintChecker.isSMBModeEnabled()
Assert.assertEquals(true, c.reasonList.size == 3) // 2x Safety & Objectives
Assert.assertEquals(true, c.mostLimitedReasonList.size == 3) // 2x Safety & Objectives
Assert.assertEquals(false, c.value())
Assertions.assertEquals(true, c.reasonList.size == 3) // 2x Safety & Objectives
Assertions.assertEquals(true, c.mostLimitedReasonList.size == 3) // 2x Safety & Objectives
Assertions.assertEquals(false, c.value())
}
// applyBasalConstraints tests
@ -346,9 +354,9 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
// Apply all limits
val d = constraintChecker.getMaxBasalAllowed(validProfile)
Assert.assertEquals(0.8, d.value(), 0.01)
Assert.assertEquals(3, d.reasonList.size)
Assert.assertEquals("DanaR: Limiting max basal rate to 0.80 U/h because of pump limit", d.getMostLimitedReasons(aapsLogger))
Assertions.assertEquals(0.8, d.value(), 0.01)
Assertions.assertEquals(3, d.reasonList.size)
Assertions.assertEquals("DanaR: Limiting max basal rate to 0.80 U/h because of pump limit", d.getMostLimitedReasons(aapsLogger))
}
@Test
@ -373,9 +381,9 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
// Apply all limits
val i = constraintChecker.getMaxBasalPercentAllowed(validProfile)
Assert.assertEquals(200, i.value())
Assert.assertEquals(6, i.reasonList.size)
Assert.assertEquals("Safety: Limiting max percent rate to 200% because of pump limit", i.getMostLimitedReasons(aapsLogger))
Assertions.assertEquals(200, i.value())
Assertions.assertEquals(6, i.reasonList.size)
Assertions.assertEquals("Safety: Limiting max percent rate to 200% because of pump limit", i.getMostLimitedReasons(aapsLogger))
}
// applyBolusConstraints tests
@ -400,9 +408,9 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
// Apply all limits
val d = constraintChecker.getMaxBolusAllowed()
Assert.assertEquals(3.0, d.value(), 0.01)
Assert.assertEquals(4, d.reasonList.size) // 2x Safety & RS & R
Assert.assertEquals("Safety: Limiting bolus to 3.0 U because of max value in preferences", d.getMostLimitedReasons(aapsLogger))
Assertions.assertEquals(3.0, d.value(), 0.01)
Assertions.assertEquals(4, d.reasonList.size) // 2x Safety & RS & R
Assertions.assertEquals("Safety: Limiting bolus to 3.0 U because of max value in preferences", d.getMostLimitedReasons(aapsLogger))
}
// applyCarbsConstraints tests
@ -413,9 +421,9 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
// Apply all limits
val i = constraintChecker.getMaxCarbsAllowed()
Assert.assertEquals(48, i.value())
Assert.assertEquals(true, i.reasonList.size == 1)
Assert.assertEquals("Safety: Limiting carbs to 48 g because of max value in preferences", i.getMostLimitedReasons(aapsLogger))
Assertions.assertEquals(48, i.value())
Assertions.assertEquals(true, i.reasonList.size == 1)
Assertions.assertEquals("Safety: Limiting carbs to 48 g because of max value in preferences", i.getMostLimitedReasons(aapsLogger))
}
// applyMaxIOBConstraints tests
@ -430,9 +438,9 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
// Apply all limits
val d = constraintChecker.getMaxIOBAllowed()
Assert.assertEquals(1.5, d.value(), 0.01)
Assert.assertEquals(d.reasonList.toString(), 2, d.reasonList.size)
Assert.assertEquals("OpenAPSAMA: Limiting IOB to 1.5 U because of max value in preferences", d.getMostLimitedReasons(aapsLogger))
Assertions.assertEquals(1.5, d.value(), 0.01)
Assertions.assertEquals(2, d.reasonList.size)
Assertions.assertEquals("OpenAPSAMA: Limiting IOB to 1.5 U because of max value in preferences", d.getMostLimitedReasons(aapsLogger))
}
@Test
@ -446,8 +454,8 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
// Apply all limits
val d = constraintChecker.getMaxIOBAllowed()
Assert.assertEquals(3.0, d.value(), 0.01)
Assert.assertEquals(d.reasonList.toString(), 2, d.reasonList.size)
Assert.assertEquals("OpenAPSSMB: Limiting IOB to 3.0 U because of max value in preferences", d.getMostLimitedReasons(aapsLogger))
Assertions.assertEquals(3.0, d.value(), 0.01)
Assertions.assertEquals(2, d.reasonList.size)
Assertions.assertEquals("OpenAPSSMB: Limiting IOB to 3.0 U because of max value in preferences", d.getMostLimitedReasons(aapsLogger))
}
}

View file

@ -4,12 +4,13 @@ import dagger.android.AndroidInjector
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.HardLimitsMock
import info.nightscout.androidaps.TestBaseWithProfile
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.database.impl.AppRepository
import info.nightscout.interfaces.ApsMode
import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.bgQualityCheck.BgQualityCheck
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.iob.GlucoseStatusProvider
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.plugin.PluginType
import info.nightscout.interfaces.profiling.Profiler
@ -22,7 +23,7 @@ import info.nightscout.plugins.constraints.safety.SafetyPlugin
import info.nightscout.plugins.pump.virtual.VirtualPumpPlugin
import info.nightscout.plugins.source.GlimpPlugin
import info.nightscout.shared.sharedPreferences.SP
import org.junit.Assert
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mock
@ -38,6 +39,7 @@ class SafetyPluginTest : TestBaseWithProfile() {
@Mock lateinit var profiler: Profiler
@Mock lateinit var repository: AppRepository
@Mock lateinit var glucoseStatusProvider: GlucoseStatusProvider
@Mock lateinit var bgQualityCheck: BgQualityCheck
private lateinit var hardLimits: HardLimits
private lateinit var safetyPlugin: SafetyPlugin
@ -81,7 +83,7 @@ class SafetyPluginTest : TestBaseWithProfile() {
)
openAPSSMBPlugin = OpenAPSSMBPlugin(
injector, aapsLogger, rxBus, constraintChecker, rh, profileFunction, context, activePlugin, iobCobCalculator, hardLimits, profiler, sp,
dateUtil, repository, glucoseStatusProvider
dateUtil, repository, glucoseStatusProvider, bgQualityCheck
)
}
@ -90,8 +92,8 @@ class SafetyPluginTest : TestBaseWithProfile() {
pumpDescription.isTempBasalCapable = false
var c = Constraint(true)
c = safetyPlugin.isLoopInvocationAllowed(c)
Assert.assertEquals("Safety: Pump is not temp basal capable", c.getReasons(aapsLogger))
Assert.assertEquals(false, c.value())
Assertions.assertEquals("Safety: Pump is not temp basal capable", c.getReasons(aapsLogger))
Assertions.assertEquals(false, c.value())
}
@Test
@ -100,8 +102,8 @@ class SafetyPluginTest : TestBaseWithProfile() {
`when`(config.isEngineeringModeOrRelease()).thenReturn(false)
var c = Constraint(true)
c = safetyPlugin.isClosedLoopAllowed(c)
Assert.assertTrue(c.getReasons(aapsLogger).contains("Running dev version. Closed loop is disabled."))
Assert.assertEquals(false, c.value())
Assertions.assertTrue(c.getReasons(aapsLogger).contains("Running dev version. Closed loop is disabled."))
Assertions.assertEquals(false, c.value())
}
@Test
@ -109,8 +111,8 @@ class SafetyPluginTest : TestBaseWithProfile() {
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.lowercase)).thenReturn(ApsMode.OPEN.lowercase)
var c = Constraint(true)
c = safetyPlugin.isClosedLoopAllowed(c)
Assert.assertTrue(c.getReasons(aapsLogger).contains("Closed loop mode disabled in preferences"))
Assert.assertEquals(false, c.value())
Assertions.assertTrue(c.getReasons(aapsLogger).contains("Closed loop mode disabled in preferences"))
Assertions.assertEquals(false, c.value())
}
@Test
@ -119,8 +121,8 @@ class SafetyPluginTest : TestBaseWithProfile() {
`when`(constraintChecker.isClosedLoopAllowed(anyObject())).thenReturn(Constraint(true))
var c = Constraint(true)
c = openAPSSMBPlugin.isSMBModeEnabled(c)
Assert.assertTrue(c.getReasons(aapsLogger).contains("SMB disabled in preferences"))
Assert.assertEquals(false, c.value())
Assertions.assertTrue(c.getReasons(aapsLogger).contains("SMB disabled in preferences"))
Assertions.assertEquals(false, c.value())
}
@Test
@ -129,8 +131,8 @@ class SafetyPluginTest : TestBaseWithProfile() {
`when`(constraintChecker.isClosedLoopAllowed(anyObject())).thenReturn(Constraint(false))
var c = Constraint(true)
c = safetyPlugin.isSMBModeEnabled(c)
Assert.assertTrue(c.getReasons(aapsLogger).contains("SMB not allowed in open loop mode"))
Assert.assertEquals(false, c.value())
Assertions.assertTrue(c.getReasons(aapsLogger).contains("SMB not allowed in open loop mode"))
Assertions.assertEquals(false, c.value())
}
@Test
@ -138,8 +140,8 @@ class SafetyPluginTest : TestBaseWithProfile() {
`when`(activePlugin.activeBgSource).thenReturn(glimpPlugin)
var c = Constraint(true)
c = safetyPlugin.isAdvancedFilteringEnabled(c)
Assert.assertEquals("Safety: SMB always and after carbs disabled because active BG source doesn\\'t support advanced filtering", c.getReasons(aapsLogger))
Assert.assertEquals(false, c.value())
Assertions.assertEquals("Safety: SMB always and after carbs disabled because active BG source doesn\\'t support advanced filtering", c.getReasons(aapsLogger))
Assertions.assertEquals(false, c.value())
}
@Test
@ -150,13 +152,13 @@ class SafetyPluginTest : TestBaseWithProfile() {
`when`(sp.getString(info.nightscout.core.utils.R.string.key_age, "")).thenReturn("child")
val c = Constraint(Constants.REALLYHIGHBASALRATE)
safetyPlugin.applyBasalConstraints(c, validProfile)
Assert.assertEquals(2.0, c.value(), 0.01)
Assert.assertEquals(
Assertions.assertEquals(2.0, c.value(), 0.01)
Assertions.assertEquals(
"""
Safety: Limiting max basal rate to 2.00 U/h because of hard limit
""".trimIndent(), c.getReasons(aapsLogger)
)
Assert.assertEquals("Safety: Limiting max basal rate to 2.00 U/h because of hard limit", c.getMostLimitedReasons(aapsLogger))
Assertions.assertEquals("Safety: Limiting max basal rate to 2.00 U/h because of hard limit", c.getMostLimitedReasons(aapsLogger))
}
@Test
@ -164,8 +166,8 @@ class SafetyPluginTest : TestBaseWithProfile() {
`when`(sp.getString(info.nightscout.core.utils.R.string.key_age, "")).thenReturn("child")
val d = Constraint(-0.5)
safetyPlugin.applyBasalConstraints(d, validProfile)
Assert.assertEquals(0.0, d.value(), 0.01)
Assert.assertEquals("Safety: Limiting max basal rate to 0.00 U/h because of it must be positive value", d.getReasons(aapsLogger))
Assertions.assertEquals(0.0, d.value(), 0.01)
Assertions.assertEquals("Safety: Limiting max basal rate to 0.00 U/h because of it must be positive value", d.getReasons(aapsLogger))
}
@Test
@ -177,8 +179,8 @@ class SafetyPluginTest : TestBaseWithProfile() {
`when`(sp.getString(info.nightscout.core.utils.R.string.key_age, "")).thenReturn("child")
val i = Constraint(Constants.REALLYHIGHPERCENTBASALRATE)
safetyPlugin.applyBasalPercentConstraints(i, validProfile)
Assert.assertEquals(200, i.value())
Assert.assertEquals(
Assertions.assertEquals(200, i.value())
Assertions.assertEquals(
"""
Safety: Percent rate 1111111% recalculated to 11111.11 U/h with current basal 1.00 U/h
Safety: Limiting max basal rate to 2.00 U/h because of hard limit
@ -186,7 +188,7 @@ Safety: Limiting max percent rate to 200% because of pump limit
Safety: Limiting max basal rate to 500.00 U/h because of pump limit
""".trimIndent(), i.getReasons(aapsLogger)
)
Assert.assertEquals("Safety: Limiting max percent rate to 200% because of pump limit", i.getMostLimitedReasons(aapsLogger))
Assertions.assertEquals("Safety: Limiting max percent rate to 200% because of pump limit", i.getMostLimitedReasons(aapsLogger))
}
@Test
@ -199,15 +201,15 @@ Safety: Limiting max basal rate to 500.00 U/h because of pump limit
openAPSSMBPlugin.setPluginEnabled(PluginType.APS, true)
val i = Constraint(Constants.REALLYHIGHBASALRATE)
openAPSSMBPlugin.applyBasalConstraints(i, validProfile)
Assert.assertEquals(1.0, i.value(), 0.01)
Assert.assertEquals(
Assertions.assertEquals(1.0, i.value(), 0.01)
Assertions.assertEquals(
"""
OpenAPSSMB: Limiting max basal rate to 1.00 U/h because of max value in preferences
OpenAPSSMB: Limiting max basal rate to 4.00 U/h because of max basal multiplier
OpenAPSSMB: Limiting max basal rate to 3.00 U/h because of max daily basal multiplier
""".trimIndent(), i.getReasons(aapsLogger)
)
Assert.assertEquals("OpenAPSSMB: Limiting max basal rate to 1.00 U/h because of max value in preferences", i.getMostLimitedReasons(aapsLogger))
Assertions.assertEquals("OpenAPSSMB: Limiting max basal rate to 1.00 U/h because of max value in preferences", i.getMostLimitedReasons(aapsLogger))
}
@Test
@ -215,15 +217,15 @@ Safety: Limiting max basal rate to 500.00 U/h because of pump limit
`when`(sp.getString(info.nightscout.core.utils.R.string.key_age, "")).thenReturn("child")
val i = Constraint(-22)
safetyPlugin.applyBasalPercentConstraints(i, validProfile)
Assert.assertEquals(0, i.value())
Assert.assertEquals(
Assertions.assertEquals(0, i.value())
Assertions.assertEquals(
"""
Safety: Percent rate -22% recalculated to -0.22 U/h with current basal 1.00 U/h
Safety: Limiting max basal rate to 0.00 U/h because of it must be positive value
Safety: Limiting max percent rate to 0% because of pump limit
""".trimIndent(), i.getReasons(aapsLogger)
)
Assert.assertEquals("Safety: Limiting max percent rate to 0% because of pump limit", i.getMostLimitedReasons(aapsLogger))
Assertions.assertEquals("Safety: Limiting max percent rate to 0% because of pump limit", i.getMostLimitedReasons(aapsLogger))
}
@Test
@ -232,14 +234,14 @@ Safety: Limiting max basal rate to 500.00 U/h because of pump limit
`when`(sp.getString(info.nightscout.core.utils.R.string.key_age, "")).thenReturn("child")
var d = Constraint(Constants.REALLYHIGHBOLUS)
d = safetyPlugin.applyBolusConstraints(d)
Assert.assertEquals(3.0, d.value(), 0.01)
Assert.assertEquals(
Assertions.assertEquals(3.0, d.value(), 0.01)
Assertions.assertEquals(
"""
Safety: Limiting bolus to 3.0 U because of max value in preferences
Safety: Limiting bolus to 5.0 U because of hard limit
""".trimIndent(), d.getReasons(aapsLogger)
)
Assert.assertEquals("Safety: Limiting bolus to 3.0 U because of max value in preferences", d.getMostLimitedReasons(aapsLogger))
Assertions.assertEquals("Safety: Limiting bolus to 3.0 U because of max value in preferences", d.getMostLimitedReasons(aapsLogger))
}
@Test
@ -248,9 +250,9 @@ Safety: Limiting max basal rate to 500.00 U/h because of pump limit
`when`(sp.getString(info.nightscout.core.utils.R.string.key_age, "")).thenReturn("child")
var d = Constraint(-22.0)
d = safetyPlugin.applyBolusConstraints(d)
Assert.assertEquals(0.0, d.value(), 0.01)
Assert.assertEquals("Safety: Limiting bolus to 0.0 U because of it must be positive value", d.getReasons(aapsLogger))
Assert.assertEquals("Safety: Limiting bolus to 0.0 U because of it must be positive value", d.getMostLimitedReasons(aapsLogger))
Assertions.assertEquals(0.0, d.value(), 0.01)
Assertions.assertEquals("Safety: Limiting bolus to 0.0 U because of it must be positive value", d.getReasons(aapsLogger))
Assertions.assertEquals("Safety: Limiting bolus to 0.0 U because of it must be positive value", d.getMostLimitedReasons(aapsLogger))
}
@Test
@ -261,13 +263,13 @@ Safety: Limiting max basal rate to 500.00 U/h because of pump limit
// Negative carbs not allowed
var i = Constraint(-22)
safetyPlugin.applyCarbsConstraints(i)
Assert.assertEquals(0, i.value())
Assert.assertEquals("Safety: Limiting carbs to 0 g because of it must be positive value", i.getReasons(aapsLogger))
Assertions.assertEquals(0, i.value())
Assertions.assertEquals("Safety: Limiting carbs to 0 g because of it must be positive value", i.getReasons(aapsLogger))
// Apply all limits
i = safetyPlugin.applyCarbsConstraints(Constraint(Constants.REALLYHIGHCARBS))
Assert.assertEquals(48, i.value())
Assert.assertEquals("Safety: Limiting carbs to 48 g because of max value in preferences", i.getReasons(aapsLogger))
Assertions.assertEquals(48, i.value())
Assertions.assertEquals("Safety: Limiting carbs to 48 g because of max value in preferences", i.getReasons(aapsLogger))
}
@Test
@ -284,22 +286,22 @@ Safety: Limiting max basal rate to 500.00 U/h because of pump limit
// Apply all limits
var d = Constraint(Constants.REALLYHIGHIOB)
d = safetyPlugin.applyMaxIOBConstraints(d)
Assert.assertEquals(HardLimits.MAX_IOB_LGS, d.value(), 0.01)
Assert.assertEquals("Safety: Limiting IOB to 0.0 U because of Low Glucose Suspend", d.getReasons(aapsLogger))
Assert.assertEquals("Safety: Limiting IOB to 0.0 U because of Low Glucose Suspend", d.getMostLimitedReasons(aapsLogger))
Assertions.assertEquals(HardLimits.MAX_IOB_LGS, d.value(), 0.01)
Assertions.assertEquals("Safety: Limiting IOB to 0.0 U because of Low Glucose Suspend", d.getReasons(aapsLogger))
Assertions.assertEquals("Safety: Limiting IOB to 0.0 U because of Low Glucose Suspend", d.getMostLimitedReasons(aapsLogger))
// Apply all limits
d = Constraint(Constants.REALLYHIGHIOB)
val a = openAPSAMAPlugin.applyMaxIOBConstraints(d)
Assert.assertEquals(1.5, a.value(), 0.01)
Assert.assertEquals("OpenAPSAMA: Limiting IOB to 1.5 U because of max value in preferences\nOpenAPSAMA: Limiting IOB to 7.0 U because of hard limit", d.getReasons(aapsLogger))
Assert.assertEquals("OpenAPSAMA: Limiting IOB to 1.5 U because of max value in preferences", d.getMostLimitedReasons(aapsLogger))
Assertions.assertEquals(1.5, a.value(), 0.01)
Assertions.assertEquals("OpenAPSAMA: Limiting IOB to 1.5 U because of max value in preferences\nOpenAPSAMA: Limiting IOB to 7.0 U because of hard limit", d.getReasons(aapsLogger))
Assertions.assertEquals("OpenAPSAMA: Limiting IOB to 1.5 U because of max value in preferences", d.getMostLimitedReasons(aapsLogger))
// Apply all limits
d = Constraint(Constants.REALLYHIGHIOB)
val s = openAPSSMBPlugin.applyMaxIOBConstraints(d)
Assert.assertEquals(3.0, s.value(), 0.01)
Assert.assertEquals("OpenAPSSMB: Limiting IOB to 3.0 U because of max value in preferences\nOpenAPSSMB: Limiting IOB to 22.0 U because of hard limit", d.getReasons(aapsLogger))
Assert.assertEquals("OpenAPSSMB: Limiting IOB to 3.0 U because of max value in preferences", d.getMostLimitedReasons(aapsLogger))
Assertions.assertEquals(3.0, s.value(), 0.01)
Assertions.assertEquals("OpenAPSSMB: Limiting IOB to 3.0 U because of max value in preferences\nOpenAPSSMB: Limiting IOB to 22.0 U because of hard limit", d.getReasons(aapsLogger))
Assertions.assertEquals("OpenAPSSMB: Limiting IOB to 3.0 U because of max value in preferences", d.getMostLimitedReasons(aapsLogger))
}
}

View file

@ -9,7 +9,7 @@ import info.nightscout.plugins.aps.loop.extensions.json
import info.nightscout.plugins.extensions.toText
import info.nightscout.plugins.sync.nsShared.extensions.log
import org.json.JSONObject
import org.junit.Assert
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mockito.`when`
@ -43,91 +43,84 @@ class PumpEnactResultTest : TestBaseWithProfile() {
val per = PumpEnactResult(injector)
per.success(true)
Assert.assertEquals(true, per.success)
Assertions.assertEquals(true, per.success)
}
@Test fun enactedTest() {
val per = PumpEnactResult(injector)
per.enacted(true)
Assert.assertEquals(true, per.enacted)
Assertions.assertEquals(true, per.enacted)
}
@Test fun commentTest() {
val per = PumpEnactResult(injector)
per.comment("SomeComment")
Assert.assertEquals("SomeComment", per.comment)
Assertions.assertEquals("SomeComment", per.comment)
}
@Test fun durationTest() {
val per = PumpEnactResult(injector)
per.duration(10)
Assert.assertEquals(10, per.duration.toLong())
Assertions.assertEquals(10, per.duration.toLong())
}
@Test fun absoluteTest() {
val per = PumpEnactResult(injector)
per.absolute(11.0)
Assert.assertEquals(11.0, per.absolute, 0.01)
Assertions.assertEquals(11.0, per.absolute, 0.01)
}
@Test fun percentTest() {
val per = PumpEnactResult(injector)
per.percent(10)
Assert.assertEquals(10, per.percent)
Assertions.assertEquals(10, per.percent)
}
@Test fun isPercentTest() {
val per = PumpEnactResult(injector)
per.isPercent(true)
Assert.assertEquals(true, per.isPercent)
Assertions.assertEquals(true, per.isPercent)
}
@Test fun isTempCancelTest() {
val per = PumpEnactResult(injector)
per.isTempCancel(true)
Assert.assertEquals(true, per.isTempCancel)
Assertions.assertEquals(true, per.isTempCancel)
}
@Test fun bolusDeliveredTest() {
val per = PumpEnactResult(injector)
per.bolusDelivered(11.0)
Assert.assertEquals(11.0, per.bolusDelivered, 0.01)
}
@Test fun carbsDeliveredTest() {
val per = PumpEnactResult(injector)
per.carbsDelivered(11.0)
Assert.assertEquals(11.0, per.carbsDelivered, 0.01)
Assertions.assertEquals(11.0, per.bolusDelivered, 0.01)
}
@Test fun queuedTest() {
val per = PumpEnactResult(injector)
per.queued(true)
Assert.assertEquals(true, per.queued)
Assertions.assertEquals(true, per.queued)
}
@Test fun logTest() {
val per = PumpEnactResult(injector)
Assert.assertEquals(
"Success: false Enacted: false Comment: Duration: -1 Absolute: -1.0 Percent: -1 IsPercent: false IsTempCancel: false bolusDelivered: 0.0 carbsDelivered: 0.0 Queued: false",
Assertions.assertEquals(
"Success: false Enacted: false Comment: Duration: -1 Absolute: -1.0 Percent: -1 IsPercent: false IsTempCancel: false bolusDelivered: 0.0 Queued: false",
per.log()
)
}
@Test fun toStringTest() {
var per = PumpEnactResult(injector).enacted(true).bolusDelivered(10.0).comment("AAA")
Assert.assertEquals(
Assertions.assertEquals(
"""
Success: false
Enacted: true
@ -136,7 +129,7 @@ class PumpEnactResultTest : TestBaseWithProfile() {
""".trimIndent(), per.toText(rh)
)
per = PumpEnactResult(injector).enacted(true).isTempCancel(true).comment("AAA")
Assert.assertEquals(
Assertions.assertEquals(
"""
Success: false
Enacted: true
@ -145,7 +138,7 @@ class PumpEnactResultTest : TestBaseWithProfile() {
""".trimIndent(), per.toText(rh)
)
per = PumpEnactResult(injector).enacted(true).isPercent(true).percent(90).duration(20).comment("AAA")
Assert.assertEquals(
Assertions.assertEquals(
"""
Success: false
Enacted: true
@ -155,7 +148,7 @@ class PumpEnactResultTest : TestBaseWithProfile() {
""".trimIndent(), per.toText(rh)
)
per = PumpEnactResult(injector).enacted(true).isPercent(false).absolute(1.0).duration(30).comment("AAA")
Assert.assertEquals(
Assertions.assertEquals(
"""
Success: false
Enacted: true
@ -165,7 +158,7 @@ class PumpEnactResultTest : TestBaseWithProfile() {
""".trimIndent(), per.toText(rh)
)
per = PumpEnactResult(injector).enacted(false).comment("AAA")
Assert.assertEquals(
Assertions.assertEquals(
"""
Success: false
Comment: AAA
@ -176,15 +169,15 @@ class PumpEnactResultTest : TestBaseWithProfile() {
@Test fun toHtmlTest() {
var per: PumpEnactResult = PumpEnactResult(injector).enacted(true).bolusDelivered(10.0).comment("AAA")
Assert.assertEquals("<b>Success</b>: false<br><b>Enacted</b>: true<br><b>Comment</b>: AAA<br><b>SMB</b>: 10.0 U", per.toHtml(rh))
Assertions.assertEquals("<b>Success</b>: false<br><b>Enacted</b>: true<br><b>Comment</b>: AAA<br><b>SMB</b>: 10.0 U", per.toHtml(rh))
per = PumpEnactResult(injector).enacted(true).isTempCancel(true).comment("AAA")
Assert.assertEquals("<b>Success</b>: false<br><b>Enacted</b>: true<br><b>Comment</b>: AAA<br>Cancel temp basal", per.toHtml(rh))
Assertions.assertEquals("<b>Success</b>: false<br><b>Enacted</b>: true<br><b>Comment</b>: AAA<br>Cancel temp basal", per.toHtml(rh))
per = PumpEnactResult(injector).enacted(true).isPercent(true).percent(90).duration(20).comment("AAA")
Assert.assertEquals("<b>Success</b>: false<br><b>Enacted</b>: true<br><b>Comment</b>: AAA<br><b>Duration</b>: 20 min<br><b>Percent</b>: 90%", per.toHtml(rh))
Assertions.assertEquals("<b>Success</b>: false<br><b>Enacted</b>: true<br><b>Comment</b>: AAA<br><b>Duration</b>: 20 min<br><b>Percent</b>: 90%", per.toHtml(rh))
per = PumpEnactResult(injector).enacted(true).isPercent(false).absolute(1.0).duration(30).comment("AAA")
Assert.assertEquals("<b>Success</b>: false<br><b>Enacted</b>: true<br><b>Comment</b>: AAA<br><b>Duration</b>: 30 min<br><b>Absolute</b>: 1.00 U/h", per.toHtml(rh))
Assertions.assertEquals("<b>Success</b>: false<br><b>Enacted</b>: true<br><b>Comment</b>: AAA<br><b>Duration</b>: 30 min<br><b>Absolute</b>: 1.00 U/h", per.toHtml(rh))
per = PumpEnactResult(injector).enacted(false).comment("AAA")
Assert.assertEquals("<b>Success</b>: false<br><b>Comment</b>: AAA", per.toHtml(rh))
Assertions.assertEquals("<b>Success</b>: false<br><b>Comment</b>: AAA", per.toHtml(rh))
}
@Test fun jsonTest() {

View file

@ -12,7 +12,7 @@ buildscript {
dagger_version = '2.44.2'
coroutines_version = '1.6.4'
activity_version = '1.6.1'
fragmentktx_version = '1.5.4'
fragmentktx_version = '1.5.5'
ormLite_version = '4.46'
gson_version = '2.10'
nav_version = '2.5.3'
@ -32,7 +32,7 @@ buildscript {
swipe_version = '1.1.0'
junit_version = '4.13.2'
junit_jupiter_version = '5.7.0'
junit_jupiter_version = '5.9.1'
mockito_version = '4.6.1'
dexmaker_version = '1.2'
retrofit2_version = '2.9.0'

View file

@ -30,7 +30,7 @@ interface DetermineBasalAdapter {
microBolusAllowed: Boolean = false,
uamAllowed: Boolean = false,
advancedFiltering: Boolean = false,
isSaveCgmSource: Boolean = false
flatBGsDetected: Boolean = false
)
operator fun invoke(): APSResult?

View file

@ -3,6 +3,15 @@ package info.nightscout.interfaces.bgQualityCheck
import androidx.annotation.DrawableRes
interface BgQualityCheck {
enum class State {
UNKNOWN,
FIVE_MIN_DATA,
RECALCULATED,
DOUBLED,
FLAT // stale data for 45 min
}
var state: State
var message: String
@DrawableRes fun icon(): Int
fun stateDescription(): String

View file

@ -0,0 +1,6 @@
package info.nightscout.interfaces.iob
interface GlucoseStatusProvider {
val glucoseStatusData: GlucoseStatus?
fun getGlucoseStatusData(allowOldData: Boolean = false): GlucoseStatus?
}

View file

@ -25,7 +25,6 @@ class PumpEnactResult(injector: HasAndroidInjector) {
// Result of treatment delivery
var bolusDelivered = 0.0 // real value of delivered insulin
var carbsDelivered = 0.0 // real value of delivered carbs
var queued = false
fun success(success: Boolean): PumpEnactResult = this.also { this.success = success }
@ -38,6 +37,5 @@ class PumpEnactResult(injector: HasAndroidInjector) {
fun isPercent(isPercent: Boolean): PumpEnactResult = this.also { it.isPercent = isPercent }
fun isTempCancel(isTempCancel: Boolean): PumpEnactResult = this.also { it.isTempCancel = isTempCancel }
fun bolusDelivered(bolusDelivered: Double): PumpEnactResult = this.also { it.bolusDelivered = bolusDelivered }
fun carbsDelivered(carbsDelivered: Double): PumpEnactResult = this.also { it.carbsDelivered = carbsDelivered }
fun queued(queued: Boolean): PumpEnactResult = this.also { it.queued = queued }
}

View file

@ -0,0 +1,3 @@
package info.nightscout.interfaces.source
interface DexcomBoyda

View file

@ -6,7 +6,6 @@ import com.google.common.base.Joiner
import dagger.android.HasAndroidInjector
import info.nightscout.core.extensions.highValueToUnitsToString
import info.nightscout.core.extensions.lowValueToUnitsToString
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.core.iob.round
import info.nightscout.core.ui.dialogs.OKDialog
import info.nightscout.core.utils.extensions.formatColor
@ -24,6 +23,7 @@ import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.db.PersistenceLayer
import info.nightscout.interfaces.iob.GlucoseStatus
import info.nightscout.interfaces.iob.GlucoseStatusProvider
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.logging.UserEntryLogger
import info.nightscout.interfaces.plugin.ActivePlugin

View file

@ -3,13 +3,13 @@ package info.nightscout.core.wizard
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.core.extensions.valueToUnits
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.core.iob.round
import info.nightscout.core.utils.MidnightUtils
import info.nightscout.database.ValueWrapper
import info.nightscout.database.entities.GlucoseValue
import info.nightscout.interfaces.aps.Loop
import info.nightscout.interfaces.db.PersistenceLayer
import info.nightscout.interfaces.iob.GlucoseStatusProvider
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.profile.Profile

View file

@ -1,107 +0,0 @@
package info.nightscout.androidaps
import androidx.collection.ArrayMap
import dagger.android.HasAndroidInjector
import info.nightscout.core.extensions.pureProfileFromJson
import info.nightscout.core.profile.ProfileSealed
import info.nightscout.interfaces.Config
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.profile.ProfileStore
import info.nightscout.interfaces.profile.PureProfile
import info.nightscout.interfaces.utils.HardLimits
import info.nightscout.interfaces.utils.JsonHelper
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.utils.DateUtil
import org.json.JSONException
import org.json.JSONObject
import javax.inject.Inject
class ProfileStoreObject(val injector: HasAndroidInjector, override val data: JSONObject, val dateUtil: DateUtil) : ProfileStore {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var config: Config
@Inject lateinit var rh: ResourceHelper
@Inject lateinit var rxBus: RxBus
@Inject lateinit var hardLimits: HardLimits
init {
injector.androidInjector().inject(this)
}
private val cachedObjects = ArrayMap<String, PureProfile>()
private fun storeUnits(): String? = JsonHelper.safeGetStringAllowNull(data, "units", null)
private fun getStore(): JSONObject? {
try {
if (data.has("store")) return data.getJSONObject("store")
} catch (e: JSONException) {
aapsLogger.error("Unhandled exception", e)
}
return null
}
override fun getStartDate(): Long {
val iso = JsonHelper.safeGetString(data, "startDate") ?: return 0
return try {
dateUtil.fromISODateString(iso)
} catch (e: Exception) {
0
}
}
override fun getDefaultProfile(): PureProfile? = getDefaultProfileName()?.let { getSpecificProfile(it) }
override fun getDefaultProfileJson(): JSONObject? = getDefaultProfileName()?.let { getSpecificProfileJson(it) }
override fun getDefaultProfileName(): String? {
val defaultProfileName = data.optString("defaultProfile")
return if (defaultProfileName.isNotEmpty()) getStore()?.has(defaultProfileName)?.let { defaultProfileName } else null
}
override fun getProfileList(): ArrayList<CharSequence> {
val ret = ArrayList<CharSequence>()
getStore()?.keys()?.let { keys ->
while (keys.hasNext()) {
val profileName = keys.next() as String
ret.add(profileName)
}
}
return ret
}
@Synchronized
override fun getSpecificProfile(profileName: String): PureProfile? {
var profile: PureProfile? = null
val units = JsonHelper.safeGetStringAllowNull(data, "units", storeUnits())
getStore()?.let { store ->
if (store.has(profileName)) {
profile = cachedObjects[profileName]
if (profile == null) {
JsonHelper.safeGetJSONObject(store, profileName, null)?.let { profileObject ->
profile = pureProfileFromJson(profileObject, dateUtil, units)
profile?.let { cachedObjects[profileName] = profile }
}
}
}
}
return profile
}
private fun getSpecificProfileJson(profileName: String): JSONObject? {
getStore()?.let { store ->
if (store.has(profileName))
return JsonHelper.safeGetJSONObject(store, profileName, null)
}
return null
}
override val allProfilesValid: Boolean
get() = getProfileList()
.asSequence()
.map { profileName -> getSpecificProfile(profileName.toString()) }
.map { pureProfile -> pureProfile?.let { ProfileSealed.Pure(pureProfile).isValid("allProfilesValid", activePlugin.activePump, config, rh, rxBus, hardLimits, false) } }
.all { it?.isValid == true }
}

View file

@ -10,7 +10,6 @@ import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.profile.DefaultValueHelper
import info.nightscout.interfaces.profile.Profile
import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.interfaces.profile.ProfileStore
import info.nightscout.interfaces.utils.HardLimits
import info.nightscout.rx.bus.RxBus
import info.nightscout.shared.interfaces.ResourceHelper
@ -38,18 +37,7 @@ open class TestBaseWithProfile : TestBase() {
val rxBus = RxBus(aapsSchedulers, aapsLogger)
val profileInjector = HasAndroidInjector {
AndroidInjector {
if (it is ProfileStoreObject) {
it.aapsLogger = aapsLogger
it.activePlugin = activePluginProvider
it.config = config
it.rh = rh
it.rxBus = rxBus
it.hardLimits = hardLimits
}
}
}
val profileInjector = HasAndroidInjector { AndroidInjector { } }
private lateinit var invalidProfileJSON: String
private lateinit var validProfileJSON: String
@ -70,32 +58,4 @@ open class TestBaseWithProfile : TestBase() {
`when`(activePluginProvider.activePump).thenReturn(testPumpPlugin)
hardLimits = HardLimitsMock(sp, rh)
}
fun getValidProfileStore(): ProfileStore {
val json = JSONObject()
val store = JSONObject()
store.put(TESTPROFILENAME, JSONObject(validProfileJSON))
json.put("defaultProfile", TESTPROFILENAME)
json.put("store", store)
return ProfileStoreObject(profileInjector, json, dateUtil)
}
fun getInvalidProfileStore1(): ProfileStore {
val json = JSONObject()
val store = JSONObject()
store.put(TESTPROFILENAME, JSONObject(invalidProfileJSON))
json.put("defaultProfile", TESTPROFILENAME)
json.put("store", store)
return ProfileStoreObject(profileInjector, json, dateUtil)
}
fun getInvalidProfileStore2(): ProfileStore {
val json = JSONObject()
val store = JSONObject()
store.put(TESTPROFILENAME, JSONObject(validProfileJSON))
store.put("invalid", JSONObject(invalidProfileJSON))
json.put("defaultProfile", TESTPROFILENAME + "invalid")
json.put("store", store)
return ProfileStoreObject(profileInjector, json, dateUtil)
}
}

View file

@ -1,316 +0,0 @@
package info.nightscout.androidaps.plugins.iob.iobCobCalculator
import android.content.Context
import info.nightscout.androidaps.TestBase
import info.nightscout.core.iob.iobCobCalculator.AutosensDataStoreObject
import info.nightscout.database.entities.GlucoseValue
import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T
import org.junit.Assert
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mock
class AutosensDataStoreTest : TestBase() {
@Mock lateinit var context: Context
lateinit var dateUtil: DateUtil
private val autosensDataStore = AutosensDataStoreObject()
@BeforeEach
fun mock() {
dateUtil = DateUtil(context)
}
@Test
fun isAbout5minDataTest() {
val bgReadingList: MutableList<GlucoseValue> = ArrayList()
// Super data should not be touched
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
// too much shifted data should return false
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(9).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(false, autosensDataStore.isAbout5minData(aapsLogger))
// too much shifted and missing data should return false
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(9).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(false, autosensDataStore.isAbout5minData(aapsLogger))
// too much shifted and missing data should return false
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(83).plus(T.secs(40)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(78).plus(T.secs(40)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(73).plus(T.secs(40)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(68).plus(T.secs(40)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(63).plus(T.secs(40)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(58).plus(T.secs(40)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(53).plus(T.secs(40)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(48).plus(T.secs(40)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(43).plus(T.secs(40)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(38).plus(T.secs(40)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(33).plus(T.secs(1)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(28).plus(T.secs(0)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(23).plus(T.secs(0)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(16).plus(T.secs(36)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(false, autosensDataStore.isAbout5minData(aapsLogger))
// slightly shifted data should return true
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).plus(T.secs(10)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
// slightly shifted and missing data should return true
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).plus(T.secs(10)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
}
@Test
fun createBucketedData5minTest1() {
val bgReadingList: MutableList<GlucoseValue> = ArrayList()
// Super data should not be touched
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(bgReadingList[0].timestamp, autosensDataStore.bucketedData!![0].timestamp)
Assert.assertEquals(bgReadingList[3].timestamp, autosensDataStore.bucketedData!![3].timestamp)
Assert.assertEquals(bgReadingList.size.toLong(), autosensDataStore.bucketedData!!.size.toLong())
// Missing value should be replaced
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).plus(T.secs(10)).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(bgReadingList[0].timestamp, autosensDataStore.bucketedData!![0].timestamp)
Assert.assertEquals(bgReadingList[2].timestamp, autosensDataStore.bucketedData!![3].timestamp)
Assert.assertEquals(bgReadingList.size + 1.toLong(), autosensDataStore.bucketedData!!.size.toLong())
// drift should be cleared
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(15).msecs() + T.secs(10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).msecs() + T.secs(10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs() + T.secs(10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(0).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(T.mins(20).msecs(), autosensDataStore.bucketedData!![0].timestamp)
Assert.assertEquals(T.mins(15).msecs(), autosensDataStore.bucketedData!![1].timestamp)
Assert.assertEquals(T.mins(10).msecs(), autosensDataStore.bucketedData!![2].timestamp)
Assert.assertEquals(T.mins(5).msecs(), autosensDataStore.bucketedData!![3].timestamp)
Assert.assertEquals(bgReadingList.size.toLong(), autosensDataStore.bucketedData!!.size.toLong())
// bucketed data should return null if not enough bg data
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(30).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(null, autosensDataStore.bucketedData)
// data should be reconstructed
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(50).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 90.0, timestamp = T.mins(45).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 40.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(T.mins(50).msecs(), autosensDataStore.bucketedData!![0].timestamp)
Assert.assertEquals(T.mins(20).msecs(), autosensDataStore.bucketedData!![6].timestamp)
Assert.assertEquals(7, autosensDataStore.bucketedData!!.size.toLong())
Assert.assertEquals(100.0, autosensDataStore.bucketedData!![0].value, 1.0)
Assert.assertEquals(90.0, autosensDataStore.bucketedData!![1].value, 1.0)
Assert.assertEquals(50.0, autosensDataStore.bucketedData!![5].value, 1.0)
Assert.assertEquals(40.0, autosensDataStore.bucketedData!![6].value, 1.0)
// non 5min data should be reconstructed
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(50).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 96.0, timestamp = T.mins(48).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 40.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(false, autosensDataStore.isAbout5minData(aapsLogger))
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(T.mins(50).msecs(), autosensDataStore.bucketedData!![0].timestamp)
Assert.assertEquals(T.mins(20).msecs(), autosensDataStore.bucketedData!![6].timestamp)
Assert.assertEquals(7, autosensDataStore.bucketedData!!.size.toLong())
Assert.assertEquals(100.0, autosensDataStore.bucketedData!![0].value, 1.0)
Assert.assertEquals(90.0, autosensDataStore.bucketedData!![1].value, 1.0)
Assert.assertEquals(50.0, autosensDataStore.bucketedData!![5].value, 1.0)
Assert.assertEquals(40.0, autosensDataStore.bucketedData!![6].value, 1.0)
}
@Test
fun createBucketedData5minTest2() {
val bgReadingList: MutableList<GlucoseValue> = ArrayList()
//bucketed data should be null if no bg data available
autosensDataStore.bgReadings = ArrayList()
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(null, autosensDataStore.bucketedData)
// real data gap test
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T13:34:55Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T13:14:55Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T13:09:55Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T13:04:55Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T12:59:55Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T12:54:55Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T12:49:55Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T12:44:55Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T12:39:55Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T12:34:55Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T12:29:56Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T12:24:55Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T12:19:56Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T12:14:56Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T12:09:56Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T12:04:56Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T11:59:55Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T04:29:57Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T04:24:56Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T04:19:57Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T04:14:57Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T04:10:03Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T04:04:56Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T03:59:56Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T03:54:56Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T03:50:03Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-09-05T03:44:57Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
autosensDataStore.bgReadings = bgReadingList
autosensDataStore.referenceTime = -1
Assert.assertEquals(true, autosensDataStore.isAbout5minData(aapsLogger))
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(dateUtil.fromISODateString("2018-09-05T13:34:57Z"), autosensDataStore.bucketedData!![0].timestamp)
Assert.assertEquals(dateUtil.fromISODateString("2018-09-05T03:44:57Z"), autosensDataStore.bucketedData!![autosensDataStore.bucketedData!!.size - 1].timestamp)
// 5min 4sec data
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T06:33:40Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T06:28:36Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T06:23:32Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T06:18:28Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T06:13:24Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T06:08:19Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T06:03:16Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T05:58:11Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T05:53:07Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T05:48:03Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T05:42:58Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T05:37:54Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T05:32:51Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T05:27:46Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T05:22:42Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T05:17:38Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T05:12:33Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T05:07:29Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T05:02:26Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T04:57:21Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = dateUtil.fromISODateString("2018-10-05T04:52:17Z"), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(false, autosensDataStore.isAbout5minData(aapsLogger))
}
@Test
fun bgReadingsTest() {
val bgReadingList: List<GlucoseValue> = ArrayList()
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(bgReadingList, autosensDataStore.bgReadings)
}
@Test
fun roundUpTimeTest() {
Assert.assertEquals(T.mins(3).msecs(), autosensDataStore.roundUpTime(T.secs(155).msecs()))
}
@Test
fun findNewerTest() {
val bgReadingList: MutableList<GlucoseValue> = ArrayList()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(T.mins(10).msecs(), autosensDataStore.findNewer(T.mins(8).msecs())!!.timestamp)
Assert.assertEquals(T.mins(5).msecs(), autosensDataStore.findNewer(T.mins(5).msecs())!!.timestamp)
Assert.assertEquals(T.mins(10).msecs(), autosensDataStore.findNewer(T.mins(10).msecs())!!.timestamp)
Assert.assertEquals(T.mins(20).msecs(), autosensDataStore.findNewer(T.mins(20).msecs())!!.timestamp)
Assert.assertEquals(null, autosensDataStore.findNewer(T.mins(22).msecs()))
}
@Test
fun findOlderTest() {
val bgReadingList: MutableList<GlucoseValue> = ArrayList()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
autosensDataStore.bgReadings = bgReadingList
Assert.assertEquals(T.mins(5).msecs(), autosensDataStore.findOlder(T.mins(8).msecs())!!.timestamp)
Assert.assertEquals(T.mins(5).msecs(), autosensDataStore.findOlder(T.mins(5).msecs())!!.timestamp)
Assert.assertEquals(T.mins(10).msecs(), autosensDataStore.findOlder(T.mins(10).msecs())!!.timestamp)
Assert.assertEquals(T.mins(20).msecs(), autosensDataStore.findOlder(T.mins(20).msecs())!!.timestamp)
Assert.assertEquals(null, autosensDataStore.findOlder(T.mins(4).msecs()))
}
@Test
fun findPreviousTimeFromBucketedDataTest() {
val bgReadingList: MutableList<GlucoseValue> = ArrayList()
autosensDataStore.bgReadings = bgReadingList
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(null, autosensDataStore.findPreviousTimeFromBucketedData(1000))
// Super data should not be touched
bgReadingList.clear()
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
bgReadingList.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
autosensDataStore.bgReadings = bgReadingList
autosensDataStore.createBucketedData(aapsLogger, dateUtil)
Assert.assertEquals(null, autosensDataStore.findPreviousTimeFromBucketedData(T.mins(4).msecs()))
Assert.assertEquals(T.mins(5).msecs(), autosensDataStore.findPreviousTimeFromBucketedData(T.mins(6).msecs()))
Assert.assertEquals(T.mins(20).msecs(), autosensDataStore.findPreviousTimeFromBucketedData(T.mins(20).msecs()))
Assert.assertEquals(T.mins(20).msecs(), autosensDataStore.findPreviousTimeFromBucketedData(T.mins(25).msecs()))
}
}

View file

@ -0,0 +1,49 @@
package info.nightscout.core.ui.elements
import android.content.Context
import android.util.AttributeSet
import androidx.core.content.edit
import androidx.preference.SeekBarPreference
/**
* Variant of SeekBarPreference with built-in string->int conversion.
*
* The normal SeekBarPreference crashes if the associated value in the
* SharedPreferences is not an int. This is a problem, because AAPS
* exports all settings as strings. When importing settings again,
* the former int value becomes a string value as a consequence.
*
* For this reason, this variant exists. It tries to first read the
* initial preference value from the preferences as an int. If it is
* not an int, ClassCastException is thrown. This is caught, and the
* value is re-read as a string and then converted to an int.
*
* To use this in fragment XMLs, replace "SeekBarPreference" in them
* with "info.nightscout.core.ui.elements.IntSeekBarPreference".
*/
class IntSeekBarPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : SeekBarPreference(context, attrs) {
override fun onSetInitialValue(defaultValue: Any?) {
val actualDefaultValue = if (defaultValue == null)
0
else
(defaultValue as Int?) ?: 0
val storedValue = try {
getPersistedInt(actualDefaultValue)
} catch (_: ClassCastException) {
val keyToDelete = key
// Remove the key manually. The setValue() function that is
// used in the "value" property assignment below tries to look
// up the existing stored value if it exists. If it does exist,
// it tries to read the value - as an int. We then get another
// ClassCastException. To avoid that, first delete the existing
// value. This prevents setValue() from doing that int lookup.
sharedPreferences?.edit {
remove(keyToDelete)
}
getPersistedString(actualDefaultValue.toString()).toInt()
}
value = storedValue
}
}

View file

@ -162,6 +162,8 @@
<string name="dia">DIA</string>
<string name="ic_short">I:C</string>
<string name="isf_short">ISF</string>
<string name="canceling_tbr_failed">Rušení dočasného bazálu selhalo</string>
<string name="canceling_eb_failed">Zastavení prodlouženého bolusu selhalo</string>
<!-- Protection-->
<string name="wrongpassword">Chybné heslo</string>
<string name="wrongpin">Nesprávný PIN</string>

View file

@ -14,6 +14,7 @@
<string name="pump_base_basal_rate">%1$.2f U/h</string>
<string name="pump_not_initialized_profile_not_set">Bomba no inicializada, ¡perfil no establecido!</string>
<string name="failed_update_basal_profile">Error al actualizar el perfil basal</string>
<string name="bolus_delivered_successfully">Bolo %1$.2fU entregado correctamente</string>
<string name="no_valid_basal_rate">Tasa basal no válida leída en la bomba</string>
<string name="limiting_iob">Limitando IOB a %1$.1f U debido a %2$s</string>
<string name="loop_disabled">LAZO DESACTIVADO POR RESTRICCIONES</string>
@ -161,6 +162,8 @@
<string name="dia">DIA</string>
<string name="ic_short">IC</string>
<string name="isf_short">ISF</string>
<string name="canceling_tbr_failed">Error cancelando la basal temporal</string>
<string name="canceling_eb_failed">Error cancelando el bolo extendido</string>
<!-- Protection-->
<string name="wrongpassword">Contraseña incorrecta</string>
<string name="wrongpin">Pin erróneo</string>
@ -243,6 +246,7 @@
<!-- CarbsReq-->
<string name="carbsreq">%1$d g carbohidratos adicionales necesarios en %2$d minutos</string>
<!-- TDDStatsActivity-->
<string name="cumulative_tdd">TDD acumulativa</string>
<string name="expweight">TDD ampliada exponencialmente</string>
<string name="basalrate">Dosis Basal</string>
<string name="bolus">Bolo</string>

View file

@ -14,6 +14,7 @@
<string name="pump_base_basal_rate">%1$.2f E/t</string>
<string name="pump_not_initialized_profile_not_set">Pumpen ikke initialisert, ingen profil valgt!</string>
<string name="failed_update_basal_profile">Klarte ikke å oppdatere basalprofil</string>
<string name="bolus_delivered_successfully">Bolus %1$.2f E ble levert vellykket</string>
<string name="no_valid_basal_rate">Ingen gyldige basaldoser ble lest fra pumpen</string>
<string name="limiting_iob">Begrenser IOB til %1$.1f E på grunn av %2$s</string>
<string name="loop_disabled">LOOP DEAKTIVERT PGA BEGRENSNINGER</string>
@ -105,6 +106,7 @@
<string name="loading">Laster…</string>
<string name="notes_label">Merknader</string>
<string name="remove_button">Fjern</string>
<string name="add_new">Legg til ny</string>
<string name="addnew_above">Legg til ny over</string>
<string name="wrong_pump_data">Data kommer fra forskjellige pumper. Bytt pumpevalg for å nullstille pumpens tilstand.</string>
<string name="bg_label">BS</string>
@ -160,6 +162,8 @@
<string name="dia">DIA</string>
<string name="ic_short">IK</string>
<string name="isf_short">ISF</string>
<string name="canceling_tbr_failed">Kansellering av Temp Basal feilet</string>
<string name="canceling_eb_failed">Kansellering av forlenget bolus feilet</string>
<!-- Protection-->
<string name="wrongpassword">Feil passord</string>
<string name="wrongpin">Feil PIN-kode</string>
@ -242,6 +246,7 @@
<!-- CarbsReq-->
<string name="carbsreq">%1$d g ekstra karbohydrater kreves innen %2$d minutter</string>
<!-- TDDStatsActivity-->
<string name="cumulative_tdd">Akkumulert TDD</string>
<string name="expweight">Eksponentielt vektet TDD</string>
<string name="basalrate">Basal</string>
<string name="bolus">Bolus</string>

View file

@ -14,6 +14,7 @@
<string name="pump_base_basal_rate">%1$.2f ед/ч</string>
<string name="pump_not_initialized_profile_not_set">помпа не инициализирована, профиль не установлен</string>
<string name="failed_update_basal_profile">не удалось обновить базальный профиль</string>
<string name="bolus_delivered_successfully">Болюс %1$.2f ед. подан успешно</string>
<string name="no_valid_basal_rate">На помпе не найдены валидные записи о базалe</string>
<string name="limiting_iob">Ограничение активного инсулина IOB до %1$.1f ед. из-за %2$s</string>
<string name="loop_disabled">ЗЦ ОТМЕНЕН ОГРАНИЧЕНИЯМИ</string>
@ -46,6 +47,7 @@
<string name="success">Успешно</string>
<string name="advancedsettings_title">Дополнительные настройки</string>
<string name="extendedbolusdeliveryerror">Ошибка подачи пролонгированного болюса</string>
<string name="aps_mode_title">Режим APS</string>
<string name="extended_bolus">Пролонгированный болюс</string>
<string name="paused">на паузе</string>
<string name="tdd_total">Суммарный суточный инсулин TDD</string>
@ -104,6 +106,7 @@
<string name="loading">Загрузка…</string>
<string name="notes_label">Заметки</string>
<string name="remove_button">Удалить</string>
<string name="add_new">Добавить новый</string>
<string name="addnew_above">Добавить строку сверху</string>
<string name="wrong_pump_data">Данные поступают с другой помпы. Измените драйвер помпы, чтобы сбросить ее состояние.</string>
<string name="bg_label">ГК</string>
@ -241,6 +244,7 @@
<!-- CarbsReq-->
<string name="carbsreq">Необходимо дополнительно %1$d г углеводов в течение %2$d минут</string>
<!-- TDDStatsActivity-->
<string name="cumulative_tdd">Накопительная суточная доза TDD</string>
<string name="expweight">экспоненциально взвешенные TDD</string>
<string name="basalrate">базал</string>
<string name="bolus">Болюс</string>

View file

@ -14,6 +14,7 @@
<string name="pump_base_basal_rate">%1$.2f JI/h</string>
<string name="pump_not_initialized_profile_not_set">Pumpa nie je inicializovaná, profil nenastavený!</string>
<string name="failed_update_basal_profile">Chyba pri aktualizovaní bazálneho profilu</string>
<string name="bolus_delivered_successfully">Bolus %1$.2f JI podaný úspešne</string>
<string name="no_valid_basal_rate">Nenačítaný žiadny platný bazál z pumpy</string>
<string name="limiting_iob">IOB obmedzený na %1$.1f JI: %2$s</string>
<string name="loop_disabled">UZAVRETÝ OKRUH DEAKTIVOVANÝ OBMEDZENÍM</string>
@ -46,6 +47,7 @@
<string name="success">Úspešne</string>
<string name="advancedsettings_title">Rozšírené nastavenia</string>
<string name="extendedbolusdeliveryerror">Chyba pri podávaní predĺženého bolusu</string>
<string name="aps_mode_title">APS mód</string>
<string name="extended_bolus">Predĺžený bolus</string>
<string name="paused">Pozastavené</string>
<string name="tdd_total">CDD celkom</string>
@ -104,6 +106,7 @@
<string name="loading">Načítavanie...</string>
<string name="notes_label">Poznámky</string>
<string name="remove_button">Vymazať</string>
<string name="add_new">Pridať nový</string>
<string name="addnew_above">Pridať novú nad</string>
<string name="wrong_pump_data">Dáta prichádzajú z inej pumpy. Zmeňte ovládač pre obnovenie stavu pumpy.</string>
<string name="bg_label">Glykémia</string>
@ -241,6 +244,7 @@
<!-- CarbsReq-->
<string name="carbsreq">Požadovaných dodatočných %1$d g sacharidov v priebehu %2$d minút</string>
<!-- TDDStatsActivity-->
<string name="cumulative_tdd">Kumulatívny TDD</string>
<string name="expweight">Exponenciálne vážený TDD</string>
<string name="basalrate">Bazál</string>
<string name="bolus">Bolus</string>
@ -465,9 +469,15 @@
<string name="wizard_explain_tt_to">%1$s do %2$s</string>
<string name="wizard_pump_not_available">Pumpa nedostupná!</string>
<!-- Preferences-->
<string name="child">Dieťa</string>
<string name="teenage">Dospievajúci</string>
<string name="adult">Dospelý</string>
<string name="resistant_adult">Dospelý s nízkou citlivosťou</string>
<string name="pregnant">Tehotenstvo</string>
<string name="patient_age_summary">Prosím vyberte typ pacienta pre nastavenie bezpečnostných limitov</string>
<string name="max_bolus_title">Maximálny povolený bolus [U]</string>
<string name="max_carbs_title">Max. povolené množstvo sacharidov [g]</string>
<string name="patient_type">Typ pacienta</string>
<!-- Protection-->
<string name="unlock_settings">Odomknúť nastavenia</string>
<!-- Pumps -->

View file

@ -5,7 +5,8 @@ import info.nightscout.database.entities.Carbs
/**
* Sync the carbs from NS
*/
class SyncNsCarbsTransaction(private val carbs: List<Carbs>) : Transaction<SyncNsCarbsTransaction.TransactionResult>() {
class SyncNsCarbsTransaction(private val carbs: List<Carbs>, private val nsClientMode: Boolean) :
Transaction<SyncNsCarbsTransaction.TransactionResult>() {
override fun run(): TransactionResult {
val result = TransactionResult()
@ -24,7 +25,7 @@ class SyncNsCarbsTransaction(private val carbs: List<Carbs>) : Transaction<SyncN
result.invalidated.add(current)
}
// and change duration
if (current.duration != carb.duration) {
if (current.duration != carb.duration && nsClientMode) {
current.amount = carb.amount
current.duration = carb.duration
database.carbsDao.updateExistingEntry(current)

View file

@ -7,7 +7,7 @@ import kotlin.math.abs
/**
* Sync the Extended bolus from NS
*/
class SyncNsExtendedBolusTransaction(private val extendedBoluses: List<ExtendedBolus>) :
class SyncNsExtendedBolusTransaction(private val extendedBoluses: List<ExtendedBolus>, private val nsClientMode: Boolean) :
Transaction<SyncNsExtendedBolusTransaction.TransactionResult>() {
override fun run(): TransactionResult {
@ -28,7 +28,7 @@ class SyncNsExtendedBolusTransaction(private val extendedBoluses: List<ExtendedB
database.extendedBolusDao.updateExistingEntry(current)
result.invalidated.add(current)
}
if (current.duration != extendedBolus.duration) {
if (current.duration != extendedBolus.duration && nsClientMode) {
current.duration = extendedBolus.duration
current.amount = extendedBolus.amount
database.extendedBolusDao.updateExistingEntry(current)

View file

@ -7,7 +7,7 @@ import kotlin.math.abs
/**
* Sync the OfflineEvent from NS
*/
class SyncNsOfflineEventTransaction(private val offlineEvents: List<OfflineEvent>) :
class SyncNsOfflineEventTransaction(private val offlineEvents: List<OfflineEvent>, private val nsClientMode: Boolean) :
Transaction<SyncNsOfflineEventTransaction.TransactionResult>() {
override fun run(): TransactionResult {
@ -28,7 +28,7 @@ class SyncNsOfflineEventTransaction(private val offlineEvents: List<OfflineEvent
database.offlineEventDao.updateExistingEntry(current)
result.invalidated.add(current)
}
if (current.duration != offlineEvent.duration) {
if (current.duration != offlineEvent.duration && nsClientMode) {
current.duration = offlineEvent.duration
database.offlineEventDao.updateExistingEntry(current)
result.updatedDuration.add(current)

View file

@ -7,7 +7,7 @@ import kotlin.math.abs
/**
* Sync the Temporary Basal from NS
*/
class SyncNsTemporaryBasalTransaction(private val temporaryBasals: List<TemporaryBasal>) : Transaction<SyncNsTemporaryBasalTransaction.TransactionResult>() {
class SyncNsTemporaryBasalTransaction(private val temporaryBasals: List<TemporaryBasal>, private val nsClientMode: Boolean) : Transaction<SyncNsTemporaryBasalTransaction.TransactionResult>() {
override fun run(): TransactionResult {
val result = TransactionResult()
@ -28,7 +28,7 @@ class SyncNsTemporaryBasalTransaction(private val temporaryBasals: List<Temporar
database.temporaryBasalDao.updateExistingEntry(current)
result.invalidated.add(current)
}
if (current.duration != temporaryBasal.duration) {
if (current.duration != temporaryBasal.duration && nsClientMode) {
current.duration = temporaryBasal.duration
database.temporaryBasalDao.updateExistingEntry(current)
result.updatedDuration.add(current)

View file

@ -7,7 +7,7 @@ import kotlin.math.abs
/**
* Sync the TemporaryTarget from NS
*/
class SyncNsTemporaryTargetTransaction(private val temporaryTargets: List<TemporaryTarget>) :
class SyncNsTemporaryTargetTransaction(private val temporaryTargets: List<TemporaryTarget>, private val nsClientMode: Boolean) :
Transaction<SyncNsTemporaryTargetTransaction.TransactionResult>() {
override fun run(): TransactionResult {
@ -28,7 +28,7 @@ class SyncNsTemporaryTargetTransaction(private val temporaryTargets: List<Tempor
database.temporaryTargetDao.updateExistingEntry(current)
result.invalidated.add(current)
}
if (current.duration != temporaryTarget.duration) {
if (current.duration != temporaryTarget.duration && nsClientMode) {
current.duration = temporaryTarget.duration
database.temporaryTargetDao.updateExistingEntry(current)
result.updatedDuration.add(current)

View file

@ -5,7 +5,7 @@ import info.nightscout.database.entities.TherapyEvent
/**
* Sync the TherapyEvents from NS
*/
class SyncNsTherapyEventTransaction(private val therapyEvents: List<TherapyEvent>) :
class SyncNsTherapyEventTransaction(private val therapyEvents: List<TherapyEvent>, private val nsClientMode: Boolean) :
Transaction<SyncNsTherapyEventTransaction.TransactionResult>() {
override fun run(): TransactionResult {
@ -24,7 +24,7 @@ class SyncNsTherapyEventTransaction(private val therapyEvents: List<TherapyEvent
database.therapyEventDao.updateExistingEntry(current)
result.invalidated.add(current)
}
if (current.duration != therapyEvent.duration) {
if (current.duration != therapyEvent.duration && nsClientMode) {
current.duration = therapyEvent.duration
database.therapyEventDao.updateExistingEntry(current)
result.updatedDuration.add(current)

View file

@ -16,6 +16,7 @@ import info.nightscout.implementation.XDripBroadcastImpl
import info.nightscout.implementation.androidNotification.NotificationHolderImpl
import info.nightscout.implementation.constraints.ConstraintsImpl
import info.nightscout.implementation.db.PersistenceLayerImpl
import info.nightscout.implementation.iob.GlucoseStatusProviderImpl
import info.nightscout.implementation.logging.LoggerUtilsImpl
import info.nightscout.implementation.overview.OverviewDataImpl
import info.nightscout.implementation.plugin.PluginStore
@ -48,6 +49,7 @@ import info.nightscout.interfaces.Translator
import info.nightscout.interfaces.XDripBroadcast
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.db.PersistenceLayer
import info.nightscout.interfaces.iob.GlucoseStatusProvider
import info.nightscout.interfaces.logging.LoggerUtils
import info.nightscout.interfaces.logging.UserEntryLogger
import info.nightscout.interfaces.plugin.ActivePlugin
@ -124,5 +126,6 @@ abstract class ImplementationModule {
@Binds fun bindsStorage(fileStorage: FileStorage): Storage
@Binds fun bindsReceiverStatusStore(receiverStatusStoreImpl: ReceiverStatusStoreImpl): ReceiverStatusStore
@Binds fun bindsUserEntryPresentationHelper(userEntryPresentationHelperImpl: UserEntryPresentationHelperImpl): UserEntryPresentationHelper
@Binds fun bindsGlucoseStatusProvider(glucoseStatusProviderImpl: GlucoseStatusProviderImpl): GlucoseStatusProvider
}
}

View file

@ -1,10 +1,11 @@
package info.nightscout.core.iob.iobCobCalculator
package info.nightscout.implementation.iob
import dagger.Reusable
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.core.iob.asRounded
import info.nightscout.core.iob.log
import info.nightscout.interfaces.iob.GlucoseStatus
import info.nightscout.interfaces.iob.GlucoseStatusProvider
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
@ -14,16 +15,16 @@ import kotlin.math.roundToLong
@Reusable
@OpenForTesting
class GlucoseStatusProvider @Inject constructor(
class GlucoseStatusProviderImpl @Inject constructor(
private val aapsLogger: AAPSLogger,
private val iobCobCalculator: IobCobCalculator,
private val dateUtil: DateUtil
) {
) : GlucoseStatusProvider {
val glucoseStatusData: GlucoseStatus?
override val glucoseStatusData: GlucoseStatus?
get() = getGlucoseStatusData()
fun getGlucoseStatusData(allowOldData: Boolean = false): GlucoseStatus? {
override fun getGlucoseStatusData(allowOldData: Boolean): GlucoseStatus? {
val data = iobCobCalculator.ads.getBgReadingsDataTableCopy()
val sizeRecords = data.size
if (sizeRecords == 0) {

View file

@ -54,7 +54,8 @@ class ProfileStoreObject(val injector: HasAndroidInjector, override val data: JS
}
override fun getDefaultProfile(): PureProfile? = getDefaultProfileName()?.let { getSpecificProfile(it) }
override fun getDefaultProfileJson(): JSONObject? = getDefaultProfileName()?.let { getSpecificProfileJson(it) }
override fun getDefaultProfileJson(): JSONObject? =
getDefaultProfileName()?.let { getSpecificProfileJson(it) }
override fun getDefaultProfileName(): String? {
val defaultProfileName = data.optString("defaultProfile")

View file

@ -24,4 +24,5 @@
<string name="detailed_14_days">Detaljert 14 dager</string>
<string name="day_tir">Dag TIR</string>
<string name="night_tir">Natt TIR</string>
<string name="carbs_short" comment="max 6 characters">Karbo</string>
</resources>

View file

@ -20,7 +20,9 @@
<string name="in_range">Dentro da meta</string>
<string name="above" comment="above &quot;in range&quot;">Acima</string>
<string name="hba1c">HbA1c: </string>
<string name="std_deviation">Desvio Padrão (DP ou SD): %1$s</string>
<string name="detailed_14_days">Detalhes de 14 dias</string>
<string name="day_tir">Tempo no Alvo (TIR) do dia</string>
<string name="night_tir">Tempo no alvo (TIR) noturno</string>
<string name="carbs_short" comment="max 6 characters">Carboidratos</string>
</resources>

View file

@ -24,4 +24,5 @@
<string name="detailed_14_days">Подробно 14 дней</string>
<string name="day_tir">Время в целевом диапазоне днем</string>
<string name="night_tir">Время в целевом диапазоне ночью</string>
<string name="carbs_short" comment="max 6 characters">Угли</string>
</resources>

View file

@ -0,0 +1,83 @@
package info.nightscout.androidaps
import info.nightscout.interfaces.utils.HardLimits
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
class HardLimitsMock @Inject constructor(
private val sp: SP,
private val rh: ResourceHelper
) : HardLimits {
companion object {
private const val CHILD = 0
private const val TEENAGE = 1
private const val ADULT = 2
private const val RESISTANT_ADULT = 3
private const val PREGNANT = 4
private val MAX_BOLUS = doubleArrayOf(5.0, 10.0, 17.0, 25.0, 60.0)
// Very Hard Limits Ranges
// First value is the Lowest and second value is the Highest a Limit can define
val VERY_HARD_LIMIT_MIN_BG = doubleArrayOf(80.0, 180.0)
val VERY_HARD_LIMIT_MAX_BG = doubleArrayOf(90.0, 200.0)
val VERY_HARD_LIMIT_TARGET_BG = doubleArrayOf(80.0, 200.0)
// Very Hard Limits Ranges for Temp Targets
val VERY_HARD_LIMIT_TEMP_MIN_BG = intArrayOf(72, 180)
val VERY_HARD_LIMIT_TEMP_MAX_BG = intArrayOf(72, 270)
val VERY_HARD_LIMIT_TEMP_TARGET_BG = intArrayOf(72, 200)
val MIN_DIA = doubleArrayOf(5.0, 5.0, 5.0, 5.0, 5.0)
val MAX_DIA = doubleArrayOf(9.0, 9.0, 9.0, 9.0, 10.0)
val MIN_IC = doubleArrayOf(2.0, 2.0, 2.0, 2.0, 0.3)
val MAX_IC = doubleArrayOf(100.0, 100.0, 100.0, 100.0, 100.0)
const val MIN_ISF = 2.0 // mgdl
const val MAX_ISF = 1000.0 // mgdl
val MAX_IOB_AMA = doubleArrayOf(3.0, 5.0, 7.0, 12.0, 25.0)
val MAX_IOB_SMB = doubleArrayOf(7.0, 13.0, 22.0, 30.0, 70.0)
val MAX_BASAL = doubleArrayOf(2.0, 5.0, 10.0, 12.0, 25.0)
//LGS Hard limits
//No IOB at all
const val MAX_IOB_LGS = 0.0
}
private fun loadAge(): Int = when (sp.getString(info.nightscout.core.utils.R.string.key_age, "")) {
rh.gs(info.nightscout.core.utils.R.string.key_child) -> CHILD
rh.gs(info.nightscout.core.utils.R.string.key_teenage) -> TEENAGE
rh.gs(info.nightscout.core.utils.R.string.key_adult) -> ADULT
rh.gs(info.nightscout.core.utils.R.string.key_resistantadult) -> RESISTANT_ADULT
rh.gs(info.nightscout.core.utils.R.string.key_pregnant) -> PREGNANT
else -> ADULT
}
override fun maxBolus(): Double = MAX_BOLUS[loadAge()]
override fun maxIobAMA(): Double = MAX_IOB_AMA[loadAge()]
override fun maxIobSMB(): Double = MAX_IOB_SMB[loadAge()]
override fun maxBasal(): Double = MAX_BASAL[loadAge()]
override fun minDia(): Double = MIN_DIA[loadAge()]
override fun maxDia(): Double = MAX_DIA[loadAge()]
override fun minIC(): Double = MIN_IC[loadAge()]
override fun maxIC(): Double = MAX_IC[loadAge()]
// safety checks
override fun checkHardLimits(value: Double, valueName: Int, lowLimit: Double, highLimit: Double): Boolean =
value == verifyHardLimits(value, valueName, lowLimit, highLimit)
override fun isInRange(value: Double, lowLimit: Double, highLimit: Double): Boolean =
value in lowLimit..highLimit
override fun verifyHardLimits(value: Double, valueName: Int, lowLimit: Double, highLimit: Double): Double {
var newValue = value
if (newValue < lowLimit || newValue > highLimit) {
newValue = max(newValue, lowLimit)
newValue = min(newValue, highLimit)
}
return newValue
}
}

View file

@ -14,8 +14,10 @@ import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.interfaces.profile.ProfileStore
import info.nightscout.interfaces.utils.HardLimits
import info.nightscout.rx.bus.RxBus
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil
import org.json.JSONObject
import org.junit.jupiter.api.BeforeEach
@ -37,25 +39,46 @@ open class TestBaseWithProfile : TestBase() {
@Mock lateinit var profileFunction: ProfileFunction
@Mock lateinit var config: Config
@Mock lateinit var context: Context
@Mock lateinit var sp: SP
private lateinit var hardLimits: HardLimits
lateinit var dateUtil: DateUtil
val rxBus = RxBus(aapsSchedulers, aapsLogger)
val profileInjector = HasAndroidInjector { AndroidInjector { } }
val profileInjector = HasAndroidInjector {
AndroidInjector {
if (it is ProfileStoreObject) {
it.aapsLogger = aapsLogger
it.activePlugin = activePluginProvider
it.config = config
it.rh = rh
it.rxBus = rxBus
it.hardLimits = hardLimits
}
}
}
private lateinit var validProfileJSON: String
private lateinit var invalidProfileJSON: String
lateinit var validProfile: ProfileSealed.Pure
lateinit var effectiveProfileSwitch: EffectiveProfileSwitch
lateinit var testPumpPlugin: TestPumpPlugin
@Suppress("PropertyName") val TESTPROFILENAME = "someProfile"
@BeforeEach
fun prepareMock() {
invalidProfileJSON = "{\"dia\":\"1\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"3\"}," +
"{\"time\":\"2:00\",\"value\":\"3.4\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4.5\"}]," +
"\"target_high\":[{\"time\":\"00:00\",\"value\":\"7\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}"
validProfileJSON = "{\"dia\":\"5\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"3\"}," +
"{\"time\":\"2:00\",\"value\":\"3.4\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4.5\"}]," +
"\"target_high\":[{\"time\":\"00:00\",\"value\":\"7\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}"
testPumpPlugin = TestPumpPlugin(profileInjector)
`when`(activePluginProvider.activePump).thenReturn(testPumpPlugin)
dateUtil = Mockito.spy(DateUtil(context))
`when`(dateUtil.now()).thenReturn(1656358822000)
hardLimits = HardLimitsMock(sp, rh)
validProfile = ProfileSealed.Pure(pureProfileFromJson(JSONObject(validProfileJSON), dateUtil)!!)
effectiveProfileSwitch = EffectiveProfileSwitch(
timestamp = dateUtil.now(),
@ -175,4 +198,23 @@ open class TestBaseWithProfile : TestBase() {
json.put("store", store)
return ProfileStoreObject(profileInjector, json, dateUtil)
}
fun getInvalidProfileStore1(): ProfileStore {
val json = JSONObject()
val store = JSONObject()
store.put(TESTPROFILENAME, JSONObject(invalidProfileJSON))
json.put("defaultProfile", TESTPROFILENAME)
json.put("store", store)
return ProfileStoreObject(profileInjector, json, dateUtil)
}
fun getInvalidProfileStore2(): ProfileStore {
val json = JSONObject()
val store = JSONObject()
store.put(TESTPROFILENAME, JSONObject(validProfileJSON))
store.put("invalid", JSONObject(invalidProfileJSON))
json.put("defaultProfile", TESTPROFILENAME + "invalid")
json.put("store", store)
return ProfileStoreObject(profileInjector, json, dateUtil)
}
}

View file

@ -26,7 +26,7 @@ class TestPumpPlugin(val injector: HasAndroidInjector) : Pump {
val lastData = 0L
val baseBasal = 0.0
override val pumpDescription = PumpDescription()
override var pumpDescription = PumpDescription()
override fun isInitialized(): Boolean = true
override fun isSuspended(): Boolean = false

View file

@ -1,8 +1,7 @@
package info.nightscout.androidaps.plugins.iob.iobCalculator
package info.nightscout.implementation.iob
import info.nightscout.androidaps.TestBase
import info.nightscout.core.iob.asRounded
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.core.iob.log
import info.nightscout.database.entities.GlucoseValue
import info.nightscout.interfaces.aps.AutosensDataStore
@ -10,16 +9,15 @@ import info.nightscout.interfaces.iob.GlucoseStatus
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T
import org.junit.Assert
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito
/**
* Created by mike on 26.03.2018.
*/
@Suppress("SpellCheckingInspection")
class GlucoseStatusTest : TestBase() {
@Mock lateinit var dateUtil: DateUtil
@ -28,85 +26,85 @@ class GlucoseStatusTest : TestBase() {
@BeforeEach
fun prepare() {
`when`(iobCobCalculatorPlugin.ads).thenReturn(autosensDataStore)
Mockito.`when`(iobCobCalculatorPlugin.ads).thenReturn(autosensDataStore)
}
@Test fun toStringShouldBeOverloaded() {
val glucoseStatus = GlucoseStatus(glucose = 0.0, noise = 0.0, delta = 0.0, shortAvgDelta = 0.0, longAvgDelta = 0.0, date = 0)
Assert.assertEquals(true, glucoseStatus.log().contains("Delta"))
Assertions.assertEquals(true, glucoseStatus.log().contains("Delta"))
}
@Test fun roundTest() {
val glucoseStatus = GlucoseStatus(glucose = 100.11111, noise = 0.0, delta = 0.0, shortAvgDelta = 0.0, longAvgDelta = 0.0, date = 0)
Assert.assertEquals(100.1, glucoseStatus.asRounded().glucose, 0.0001)
Assertions.assertEquals(100.1, glucoseStatus.asRounded().glucose, 0.0001)
}
@Test fun calculateValidGlucoseStatus() {
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateValidBgData())
val glucoseStatus = GlucoseStatusProvider(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!!
Assert.assertEquals(214.0, glucoseStatus.glucose, 0.001)
Assert.assertEquals(-2.0, glucoseStatus.delta, 0.001)
Assert.assertEquals(-2.5, glucoseStatus.shortAvgDelta, 0.001) // -2 -2.5 -3 deltas are relative to current value
Assert.assertEquals(-2.0, glucoseStatus.longAvgDelta, 0.001) // -2 -2 -2 -2
Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date
Mockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateValidBgData())
val glucoseStatus = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!!
Assertions.assertEquals(214.0, glucoseStatus.glucose, 0.001)
Assertions.assertEquals(-2.0, glucoseStatus.delta, 0.001)
Assertions.assertEquals(-2.5, glucoseStatus.shortAvgDelta, 0.001) // -2 -2.5 -3 deltas are relative to current value
Assertions.assertEquals(-2.0, glucoseStatus.longAvgDelta, 0.001) // -2 -2 -2 -2
Assertions.assertEquals(1514766900000L, glucoseStatus.date) // latest date
}
@Test fun calculateMostRecentGlucoseStatus() {
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateMostRecentBgData())
val glucoseStatus: GlucoseStatus = GlucoseStatusProvider(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!!
Assert.assertEquals(215.0, glucoseStatus.glucose, 0.001) // (214+216) / 2
Assert.assertEquals(-1.0, glucoseStatus.delta, 0.001)
Assert.assertEquals(-1.0, glucoseStatus.shortAvgDelta, 0.001)
Assert.assertEquals(0.0, glucoseStatus.longAvgDelta, 0.001)
Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date, even when averaging
Mockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateMostRecentBgData())
val glucoseStatus: GlucoseStatus = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!!
Assertions.assertEquals(215.0, glucoseStatus.glucose, 0.001) // (214+216) / 2
Assertions.assertEquals(-1.0, glucoseStatus.delta, 0.001)
Assertions.assertEquals(-1.0, glucoseStatus.shortAvgDelta, 0.001)
Assertions.assertEquals(0.0, glucoseStatus.longAvgDelta, 0.001)
Assertions.assertEquals(1514766900000L, glucoseStatus.date) // latest date, even when averaging
}
@Test fun oneRecordShouldProduceZeroDeltas() {
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateOneCurrentRecordBgData())
val glucoseStatus: GlucoseStatus = GlucoseStatusProvider(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!!
Assert.assertEquals(214.0, glucoseStatus.glucose, 0.001)
Assert.assertEquals(0.0, glucoseStatus.delta, 0.001)
Assert.assertEquals(0.0, glucoseStatus.shortAvgDelta, 0.001) // -2 -2.5 -3 deltas are relative to current value
Assert.assertEquals(0.0, glucoseStatus.longAvgDelta, 0.001) // -2 -2 -2 -2
Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date
Mockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateOneCurrentRecordBgData())
val glucoseStatus: GlucoseStatus = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!!
Assertions.assertEquals(214.0, glucoseStatus.glucose, 0.001)
Assertions.assertEquals(0.0, glucoseStatus.delta, 0.001)
Assertions.assertEquals(0.0, glucoseStatus.shortAvgDelta, 0.001) // -2 -2.5 -3 deltas are relative to current value
Assertions.assertEquals(0.0, glucoseStatus.longAvgDelta, 0.001) // -2 -2 -2 -2
Assertions.assertEquals(1514766900000L, glucoseStatus.date) // latest date
}
@Test fun insufficientDataShouldReturnNull() {
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateInsufficientBgData())
val glucoseStatus: GlucoseStatus? = GlucoseStatusProvider(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData
Assert.assertEquals(null, glucoseStatus)
Mockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateInsufficientBgData())
val glucoseStatus: GlucoseStatus? = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData
Assertions.assertEquals(null, glucoseStatus)
}
@Test fun oldDataShouldReturnNull() {
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateOldBgData())
val glucoseStatus: GlucoseStatus? = GlucoseStatusProvider(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData
Assert.assertEquals(null, glucoseStatus)
Mockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateOldBgData())
val glucoseStatus: GlucoseStatus? = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData
Assertions.assertEquals(null, glucoseStatus)
}
@Test fun returnOldDataIfAllowed() {
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateOldBgData())
val glucoseStatus: GlucoseStatus? = GlucoseStatusProvider(aapsLogger, iobCobCalculatorPlugin, dateUtil).getGlucoseStatusData(true)
Assert.assertNotEquals(null, glucoseStatus)
Mockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateOldBgData())
val glucoseStatus: GlucoseStatus? = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).getGlucoseStatusData(true)
Assertions.assertNotEquals(null, glucoseStatus)
}
@Test fun averageShouldNotFailOnEmptyArray() {
Assert.assertEquals(0.0, GlucoseStatusProvider.average(ArrayList()), 0.001)
Assertions.assertEquals(0.0, GlucoseStatusProviderImpl.average(ArrayList()), 0.001)
}
@Test fun calculateGlucoseStatusForLibreTestBgData() {
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateLibreTestData())
val glucoseStatus: GlucoseStatus = GlucoseStatusProvider(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!!
Assert.assertEquals(100.0, glucoseStatus.glucose, 0.001) //
Assert.assertEquals(-10.0, glucoseStatus.delta, 0.001)
Assert.assertEquals(-10.0, glucoseStatus.shortAvgDelta, 0.001)
Assert.assertEquals(-10.0, glucoseStatus.longAvgDelta, 0.001)
Assert.assertEquals(1514766900000L, glucoseStatus.date) // latest date
Mockito.`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(generateLibreTestData())
val glucoseStatus: GlucoseStatus = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculatorPlugin, dateUtil).glucoseStatusData!!
Assertions.assertEquals(100.0, glucoseStatus.glucose, 0.001) //
Assertions.assertEquals(-10.0, glucoseStatus.delta, 0.001)
Assertions.assertEquals(-10.0, glucoseStatus.shortAvgDelta, 0.001)
Assertions.assertEquals(-10.0, glucoseStatus.longAvgDelta, 0.001)
Assertions.assertEquals(1514766900000L, glucoseStatus.date) // latest date
}
@BeforeEach
fun initMocking() {
`when`(dateUtil.now()).thenReturn(1514766900000L + T.mins(1).msecs())
`when`(iobCobCalculatorPlugin.ads).thenReturn(autosensDataStore)
Mockito.`when`(dateUtil.now()).thenReturn(1514766900000L + T.mins(1).msecs())
Mockito.`when`(iobCobCalculatorPlugin.ads).thenReturn(autosensDataStore)
}
// [{"mgdl":214,"mills":1521895773113,"device":"xDrip-DexcomG5","direction":"Flat","filtered":191040,"unfiltered":205024,"noise":1,"rssi":100},{"mgdl":219,"mills":1521896073352,"device":"xDrip-DexcomG5","direction":"Flat","filtered":200160,"unfiltered":209760,"noise":1,"rssi":100},{"mgdl":222,"mills":1521896372890,"device":"xDrip-DexcomG5","direction":"Flat","filtered":207360,"unfiltered":212512,"noise":1,"rssi":100},{"mgdl":220,"mills":1521896673062,"device":"xDrip-DexcomG5","direction":"Flat","filtered":211488,"unfiltered":210688,"noise":1,"rssi":100},{"mgdl":193,"mills":1521896972933,"device":"xDrip-DexcomG5","direction":"Flat","filtered":212384,"unfiltered":208960,"noise":1,"rssi":100},{"mgdl":181,"mills":1521897273336,"device":"xDrip-DexcomG5","direction":"SingleDown","filtered":210592,"unfiltered":204320,"noise":1,"rssi":100},{"mgdl":176,"mills":1521897572875,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":206720,"unfiltered":197440,"noise":1,"rssi":100},{"mgdl":168,"mills":1521897872929,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":201024,"unfiltered":187904,"noise":1,"rssi":100},{"mgdl":161,"mills":1521898172814,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":193376,"unfiltered":178144,"noise":1,"rssi":100},{"mgdl":148,"mills":1521898472879,"device":"xDrip-DexcomG5","direction":"SingleDown","filtered":183264,"unfiltered":161216,"noise":1,"rssi":100},{"mgdl":139,"mills":1521898772862,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":170784,"unfiltered":148928,"noise":1,"rssi":100},{"mgdl":132,"mills":1521899072896,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":157248,"unfiltered":139552,"noise":1,"rssi":100},{"mgdl":125,"mills":1521899372834,"device":"xDrip-DexcomG5","direction":"FortyFiveDown","filtered":144416,"unfiltered":129616.00000000001,"noise":1,"rssi":100},{"mgdl":128,"mills":1521899973456,"device":"xDrip-DexcomG5","direction":"Flat","filtered":130240.00000000001,"unfiltered":133536,"noise":1,"rssi":100},{"mgdl":132,"mills":1521900573287,"device":"xDrip-DexcomG5","direction":"Flat","filtered":133504,"unfiltered":138720,"noise":1,"rssi":100},{"mgdl":127,"mills":1521900873711,"device":"xDrip-DexcomG5","direction":"Flat","filtered":136480,"unfiltered":132992,"noise":1,"rssi":100},{"mgdl":127,"mills":1521901180151,"device":"xDrip-DexcomG5","direction":"Flat","filtered":136896,"unfiltered":132128,"noise":1,"rssi":100},{"mgdl":125,"mills":1521901473582,"device":"xDrip-DexcomG5","direction":"Flat","filtered":134624,"unfiltered":129696,"noise":1,"rssi":100},{"mgdl":120,"mills":1521901773597,"device":"xDrip-DexcomG5","direction":"Flat","filtered":130704.00000000001,"unfiltered":123376,"noise":1,"rssi":100},{"mgdl":116,"mills":1521902075855,"device":"xDrip-DexcomG5","direction":"Flat","filtered":126272,"unfiltered":118448,"noise":1,"rssi":100}]
@ -154,13 +152,40 @@ class GlucoseStatusTest : TestBase() {
// Now
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = latestReading, timestamp = endTime, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
// One minute ago
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = latestReading, timestamp = endTime - 1000 * 60 * 1, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(
GlucoseValue(
raw = 0.0,
noise = 0.0,
value = latestReading,
timestamp = endTime - 1000 * 60 * 1,
sourceSensor = GlucoseValue.SourceSensor.UNKNOWN,
trendArrow = GlucoseValue.TrendArrow.FLAT
)
)
// Two minutes ago
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = latestReading, timestamp = endTime - 1000 * 60 * 2, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(
GlucoseValue(
raw = 0.0,
noise = 0.0,
value = latestReading,
timestamp = endTime - 1000 * 60 * 2,
sourceSensor = GlucoseValue.SourceSensor.UNKNOWN,
trendArrow = GlucoseValue.TrendArrow.FLAT
)
)
// Three minutes and beyond at constant rate
for (i in 3..49)
list.add(GlucoseValue(raw = 0.0, noise = 0.0, value = latestReading + i * 2, timestamp = endTime - 1000 * 60 * i, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
list.add(
GlucoseValue(
raw = 0.0,
noise = 0.0,
value = latestReading + i * 2,
timestamp = endTime - 1000 * 60 * i,
sourceSensor = GlucoseValue.SourceSensor.UNKNOWN,
trendArrow = GlucoseValue.TrendArrow.FLAT
)
)
return list
}
}

View file

@ -1,4 +1,4 @@
package info.nightscout.androidaps.interfaces
package info.nightscout.implementation.profile
import info.nightscout.androidaps.TestBaseWithProfile
import info.nightscout.interfaces.profile.PureProfile

View file

@ -37,7 +37,7 @@ import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil
import io.reactivex.rxjava3.core.Single
import org.junit.Assert
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mock
@ -51,7 +51,6 @@ class CommandQueueImplementationTest : TestBaseWithProfile() {
@Mock lateinit var constraintChecker: Constraints
@Mock lateinit var activePlugin: ActivePlugin
@Mock lateinit var sp: SP
@Mock lateinit var powerManager: PowerManager
@Mock lateinit var repository: AppRepository
@Mock lateinit var uiInteraction: UiInteraction
@ -115,7 +114,6 @@ class CommandQueueImplementationTest : TestBaseWithProfile() {
}
private lateinit var commandQueue: CommandQueueImplementation
private lateinit var testPumpPlugin: TestPumpPlugin
@BeforeEach
fun prepare() {
@ -170,108 +168,108 @@ class CommandQueueImplementationTest : TestBaseWithProfile() {
commandQueue.handler = handler
// start with empty queue
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// add bolus command
commandQueue.bolus(DetailedBolusInfo(), null)
Assert.assertEquals(1, commandQueue.size())
Assertions.assertEquals(1, commandQueue.size())
commandQueue.waitForFinishedThread()
Thread.sleep(1000)
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
}
@Test
fun doTests() {
// start with empty queue
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// add bolus command
commandQueue.bolus(DetailedBolusInfo(), null)
Assert.assertEquals(1, commandQueue.size())
Assertions.assertEquals(1, commandQueue.size())
// add READSTATUS
commandQueue.readStatus("anyString", null)
Assert.assertEquals(2, commandQueue.size())
Assertions.assertEquals(2, commandQueue.size())
// adding another bolus should remove the first one (size still == 2)
commandQueue.bolus(DetailedBolusInfo(), null)
Assert.assertEquals(2, commandQueue.size())
Assertions.assertEquals(2, commandQueue.size())
// clear the queue should reset size
commandQueue.clear()
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// add tempbasal
commandQueue.tempBasalAbsolute(0.0, 30, true, validProfile, PumpSync.TemporaryBasalType.NORMAL, null)
Assert.assertEquals(1, commandQueue.size())
Assertions.assertEquals(1, commandQueue.size())
// add tempbasal percent. it should replace previous TEMPBASAL
commandQueue.tempBasalPercent(0, 30, true, validProfile, PumpSync.TemporaryBasalType.NORMAL, null)
Assert.assertEquals(1, commandQueue.size())
Assertions.assertEquals(1, commandQueue.size())
// cancel tempbasal it should replace previous TEMPBASAL
commandQueue.cancelTempBasal(false, null)
Assert.assertEquals(1, commandQueue.size())
Assertions.assertEquals(1, commandQueue.size())
// add extended bolus
commandQueue.extendedBolus(1.0, 30, null)
Assert.assertEquals(2, commandQueue.size())
Assertions.assertEquals(2, commandQueue.size())
// add extended should remove previous extended setting
commandQueue.extendedBolus(1.0, 30, null)
Assert.assertEquals(2, commandQueue.size())
Assertions.assertEquals(2, commandQueue.size())
// cancel extended bolus should replace previous extended
commandQueue.cancelExtended(null)
Assert.assertEquals(2, commandQueue.size())
Assertions.assertEquals(2, commandQueue.size())
// add setProfile
// TODO: this crash the test
// commandQueue.setProfile(validProfile, null)
// Assert.assertEquals(3, commandQueue.size())
// Assertions.assertEquals(3, commandQueue.size())
// add loadHistory
commandQueue.loadHistory(0.toByte(), null)
Assert.assertEquals(3, commandQueue.size())
Assertions.assertEquals(3, commandQueue.size())
// add loadEvents
commandQueue.loadEvents(null)
Assert.assertEquals(4, commandQueue.size())
Assertions.assertEquals(4, commandQueue.size())
commandQueue.clear()
commandQueue.tempBasalAbsolute(0.0, 30, true, validProfile, PumpSync.TemporaryBasalType.NORMAL, null)
commandQueue.pickup()
Assert.assertEquals(0, commandQueue.size())
Assert.assertNotNull(commandQueue.performing)
Assert.assertEquals(Command.CommandType.TEMPBASAL, commandQueue.performing?.commandType)
Assertions.assertEquals(0, commandQueue.size())
Assertions.assertNotNull(commandQueue.performing)
Assertions.assertEquals(Command.CommandType.TEMPBASAL, commandQueue.performing?.commandType)
commandQueue.resetPerforming()
Assert.assertNull(commandQueue.performing)
Assertions.assertNull(commandQueue.performing)
}
@Test
fun callingCancelAllBolusesClearsQueue() {
// given
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
val smb = DetailedBolusInfo()
smb.lastKnownBolusTime = System.currentTimeMillis()
smb.bolusType = DetailedBolusInfo.BolusType.SMB
commandQueue.bolus(smb, null)
commandQueue.bolus(DetailedBolusInfo(), null)
Assert.assertEquals(2, commandQueue.size())
Assertions.assertEquals(2, commandQueue.size())
// when
commandQueue.cancelAllBoluses(null)
// then
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
}
@Test
fun smbIsRejectedIfABolusIsQueued() {
// given
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// when
commandQueue.bolus(DetailedBolusInfo(), null)
@ -280,14 +278,14 @@ class CommandQueueImplementationTest : TestBaseWithProfile() {
val queued: Boolean = commandQueue.bolus(smb, null)
// then
Assert.assertFalse(queued)
Assert.assertEquals(commandQueue.size(), 1)
Assertions.assertFalse(queued)
Assertions.assertEquals(commandQueue.size(), 1)
}
@Test
fun smbIsRejectedIfLastKnownBolusIsOutdated() {
// given
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// when
val bolus = DetailedBolusInfo()
@ -296,14 +294,14 @@ class CommandQueueImplementationTest : TestBaseWithProfile() {
val queued: Boolean = commandQueue.bolus(bolus, null)
// then
Assert.assertFalse(queued)
Assert.assertEquals(commandQueue.size(), 0)
Assertions.assertFalse(queued)
Assertions.assertEquals(commandQueue.size(), 0)
}
@Test
fun isCustomCommandRunning() {
// given
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// when
val queued1 = commandQueue.customCommand(CustomCommand1(), null)
@ -311,201 +309,201 @@ class CommandQueueImplementationTest : TestBaseWithProfile() {
commandQueue.pickup()
// then
Assert.assertTrue(queued1)
Assert.assertTrue(queued2)
Assert.assertTrue(commandQueue.isCustomCommandInQueue(CustomCommand1::class.java))
Assert.assertTrue(commandQueue.isCustomCommandInQueue(CustomCommand2::class.java))
Assert.assertFalse(commandQueue.isCustomCommandInQueue(CustomCommand3::class.java))
Assertions.assertTrue(queued1)
Assertions.assertTrue(queued2)
Assertions.assertTrue(commandQueue.isCustomCommandInQueue(CustomCommand1::class.java))
Assertions.assertTrue(commandQueue.isCustomCommandInQueue(CustomCommand2::class.java))
Assertions.assertFalse(commandQueue.isCustomCommandInQueue(CustomCommand3::class.java))
Assert.assertTrue(commandQueue.isCustomCommandRunning(CustomCommand1::class.java))
Assert.assertFalse(commandQueue.isCustomCommandRunning(CustomCommand2::class.java))
Assert.assertFalse(commandQueue.isCustomCommandRunning(CustomCommand3::class.java))
Assertions.assertTrue(commandQueue.isCustomCommandRunning(CustomCommand1::class.java))
Assertions.assertFalse(commandQueue.isCustomCommandRunning(CustomCommand2::class.java))
Assertions.assertFalse(commandQueue.isCustomCommandRunning(CustomCommand3::class.java))
Assert.assertEquals(1, commandQueue.size())
Assertions.assertEquals(1, commandQueue.size())
}
@Test
fun isSetUserOptionsCommandInQueue() {
// given
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// when
commandQueue.setUserOptions(null)
// then
Assert.assertTrue(commandQueue.isLastScheduled(Command.CommandType.SET_USER_SETTINGS))
Assert.assertEquals(1, commandQueue.size())
Assertions.assertTrue(commandQueue.isLastScheduled(Command.CommandType.SET_USER_SETTINGS))
Assertions.assertEquals(1, commandQueue.size())
// next should be ignored
commandQueue.setUserOptions(null)
Assert.assertEquals(1, commandQueue.size())
Assertions.assertEquals(1, commandQueue.size())
}
@Test
fun isLoadEventsCommandInQueue() {
// given
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// when
commandQueue.loadEvents(null)
// then
Assert.assertTrue(commandQueue.isLastScheduled(Command.CommandType.LOAD_EVENTS))
Assert.assertEquals(1, commandQueue.size())
Assertions.assertTrue(commandQueue.isLastScheduled(Command.CommandType.LOAD_EVENTS))
Assertions.assertEquals(1, commandQueue.size())
// next should be ignored
commandQueue.loadEvents(null)
Assert.assertEquals(1, commandQueue.size())
Assertions.assertEquals(1, commandQueue.size())
}
@Test
fun isLoadTDDsCommandInQueue() {
// given
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// when
commandQueue.loadTDDs(null)
// then
Assert.assertEquals(1, commandQueue.size())
Assertions.assertEquals(1, commandQueue.size())
// next should be ignored
commandQueue.loadTDDs(null)
Assert.assertEquals(1, commandQueue.size())
Assertions.assertEquals(1, commandQueue.size())
}
@Test
fun isLoadHistoryCommandInQueue() {
// given
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// when
commandQueue.loadHistory(0, null)
// then
Assert.assertTrue(commandQueue.isLastScheduled(Command.CommandType.LOAD_HISTORY))
Assert.assertEquals(1, commandQueue.size())
Assertions.assertTrue(commandQueue.isLastScheduled(Command.CommandType.LOAD_HISTORY))
Assertions.assertEquals(1, commandQueue.size())
// next should be ignored
commandQueue.loadHistory(0, null)
Assert.assertEquals(1, commandQueue.size())
Assertions.assertEquals(1, commandQueue.size())
}
@Test
fun isProfileSetCommandInQueue() {
// given
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// when
testPumpPlugin.isProfileSet = true
commandQueue.setProfile(validProfile, false, object : Callback() {
override fun run() {
Assert.assertTrue(result.success)
Assert.assertFalse(result.enacted)
Assertions.assertTrue(result.success)
Assertions.assertFalse(result.enacted)
}
})
// then
// the same profile -> ignore
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// different should be added
testPumpPlugin.isProfileSet = false
commandQueue.setProfile(validProfile, false, object : Callback() {
override fun run() {
Assert.assertTrue(result.success)
Assert.assertTrue(result.enacted)
Assertions.assertTrue(result.success)
Assertions.assertTrue(result.enacted)
}
})
Assert.assertEquals(1, commandQueue.size())
Assertions.assertEquals(1, commandQueue.size())
// next should be ignored
commandQueue.setProfile(validProfile, false, object : Callback() {
override fun run() {
Assert.assertTrue(result.success)
Assertions.assertTrue(result.success)
}
})
Assert.assertEquals(1, commandQueue.size())
Assertions.assertEquals(1, commandQueue.size())
testPumpPlugin.isProfileSet = true
}
@Test
fun isStopCommandInQueue() {
// given
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// when
commandQueue.stopPump(null)
// then
Assert.assertTrue(commandQueue.isLastScheduled(Command.CommandType.STOP_PUMP))
Assert.assertEquals(1, commandQueue.size())
Assertions.assertTrue(commandQueue.isLastScheduled(Command.CommandType.STOP_PUMP))
Assertions.assertEquals(1, commandQueue.size())
}
@Test
fun isStarCommandInQueue() {
// given
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// when
commandQueue.startPump(null)
// then
Assert.assertTrue(commandQueue.isLastScheduled(Command.CommandType.START_PUMP))
Assert.assertEquals(1, commandQueue.size())
Assertions.assertTrue(commandQueue.isLastScheduled(Command.CommandType.START_PUMP))
Assertions.assertEquals(1, commandQueue.size())
}
@Test
fun isSetTbrNotificationCommandInQueue() {
// given
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// when
commandQueue.setTBROverNotification(null, true)
// then
Assert.assertTrue(commandQueue.isLastScheduled(Command.CommandType.INSIGHT_SET_TBR_OVER_ALARM))
Assert.assertEquals(1, commandQueue.size())
Assertions.assertTrue(commandQueue.isLastScheduled(Command.CommandType.INSIGHT_SET_TBR_OVER_ALARM))
Assertions.assertEquals(1, commandQueue.size())
}
@Test
fun differentCustomCommandsAllowed() {
// given
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// when
val queued1 = commandQueue.customCommand(CustomCommand1(), null)
val queued2 = commandQueue.customCommand(CustomCommand2(), null)
// then
Assert.assertTrue(queued1)
Assert.assertTrue(queued2)
Assert.assertEquals(2, commandQueue.size())
Assertions.assertTrue(queued1)
Assertions.assertTrue(queued2)
Assertions.assertEquals(2, commandQueue.size())
}
@Test
fun sameCustomCommandNotAllowed() {
// given
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// when
val queued1 = commandQueue.customCommand(CustomCommand1(), null)
val queued2 = commandQueue.customCommand(CustomCommand1(), null)
// then
Assert.assertTrue(queued1)
Assert.assertFalse(queued2)
Assert.assertEquals(1, commandQueue.size())
Assertions.assertTrue(queued1)
Assertions.assertFalse(queued2)
Assertions.assertEquals(1, commandQueue.size())
}
@Test
fun readStatusTwiceIsNotAllowed() {
// given
Assert.assertEquals(0, commandQueue.size())
Assertions.assertEquals(0, commandQueue.size())
// when
val queued1 = commandQueue.readStatus("1", null)
val queued2 = commandQueue.readStatus("2", null)
// then
Assert.assertTrue(queued1)
Assert.assertFalse(queued2)
Assert.assertEquals(1, commandQueue.size())
Assert.assertTrue(commandQueue.statusInQueue())
Assertions.assertTrue(queued1)
Assertions.assertFalse(queued2)
Assertions.assertEquals(1, commandQueue.size())
Assertions.assertTrue(commandQueue.statusInQueue())
}
private class CustomCommand1 : CustomCommand {

View file

@ -17,7 +17,6 @@ import info.nightscout.interfaces.pump.PumpSync
import info.nightscout.interfaces.pump.defs.PumpDescription
import info.nightscout.interfaces.queue.Command
import info.nightscout.interfaces.ui.UiInteraction
import info.nightscout.shared.sharedPreferences.SP
import org.junit.Assert
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@ -29,7 +28,6 @@ class QueueThreadTest : TestBaseWithProfile() {
@Mock lateinit var constraintChecker: Constraints
@Mock lateinit var activePlugin: ActivePlugin
@Mock lateinit var sp: SP
@Mock lateinit var powerManager: PowerManager
@Mock lateinit var repository: AppRepository
@Mock lateinit var androidPermission: AndroidPermission

View file

@ -1,11 +1,12 @@
package info.nightscout.core.wizard
package info.nightscout.implementation.wizard
import android.content.Context
import dagger.android.AndroidInjector
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.TestBase
import info.nightscout.androidaps.TestPumpPlugin
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.core.wizard.BolusWizard
import info.nightscout.implementation.iob.GlucoseStatusProviderImpl
import info.nightscout.interfaces.GlucoseUnit
import info.nightscout.interfaces.aps.AutosensDataStore
import info.nightscout.interfaces.aps.Loop
@ -25,7 +26,6 @@ import org.junit.Assert
import org.junit.jupiter.api.Test
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.invocation.InvocationOnMock
class BolusWizardTest : TestBase() {
@ -56,7 +56,7 @@ class BolusWizardTest : TestBase() {
it.loop = loop
it.dateUtil = dateUtil
it.iobCobCalculator = iobCobCalculator
it.glucoseStatusProvider = GlucoseStatusProvider(aapsLogger = aapsLogger, iobCobCalculator = iobCobCalculator, dateUtil = dateUtil)
it.glucoseStatusProvider = GlucoseStatusProviderImpl(aapsLogger = aapsLogger, iobCobCalculator = iobCobCalculator, dateUtil = dateUtil)
}
}
}
@ -66,19 +66,19 @@ class BolusWizardTest : TestBase() {
@Suppress("SameParameterValue")
private fun setupProfile(targetLow: Double, targetHigh: Double, insulinSensitivityFactor: Double, insulinToCarbRatio: Double): Profile {
val profile = Mockito.mock(Profile::class.java)
`when`(profile.getTargetLowMgdl()).thenReturn(targetLow)
`when`(profile.getTargetLowMgdl()).thenReturn(targetHigh)
`when`(profile.getIsfMgdl()).thenReturn(insulinSensitivityFactor)
`when`(profile.getIc()).thenReturn(insulinToCarbRatio)
Mockito.`when`(profile.getTargetLowMgdl()).thenReturn(targetLow)
Mockito.`when`(profile.getTargetLowMgdl()).thenReturn(targetHigh)
Mockito.`when`(profile.getIsfMgdl()).thenReturn(insulinSensitivityFactor)
Mockito.`when`(profile.getIc()).thenReturn(insulinToCarbRatio)
`when`(profileFunction.getUnits()).thenReturn(GlucoseUnit.MGDL)
`when`(iobCobCalculator.calculateIobFromBolus()).thenReturn(IobTotal(System.currentTimeMillis()))
`when`(iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended()).thenReturn(IobTotal(System.currentTimeMillis()))
`when`(activePlugin.activePump).thenReturn(testPumpPlugin)
Mockito.`when`(profileFunction.getUnits()).thenReturn(GlucoseUnit.MGDL)
Mockito.`when`(iobCobCalculator.calculateIobFromBolus()).thenReturn(IobTotal(System.currentTimeMillis()))
Mockito.`when`(iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended()).thenReturn(IobTotal(System.currentTimeMillis()))
Mockito.`when`(activePlugin.activePump).thenReturn(testPumpPlugin)
testPumpPlugin.pumpDescription = PumpDescription().also {
it.bolusStep = pumpBolusStep
}
`when`(iobCobCalculator.ads).thenReturn(autosensDataStore)
Mockito.`when`(iobCobCalculator.ads).thenReturn(autosensDataStore)
Mockito.doAnswer { invocation: InvocationOnMock ->
invocation.getArgument<Constraint<Double>>(0)

View file

@ -110,7 +110,7 @@ function enable_smb(
return false;
}
var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, microBolusAllowed, reservoir_data, currentTime, isSaveCgmSource) {
var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, microBolusAllowed, reservoir_data, currentTime, flatBGsDetected) {
var rT = {}; //short for requestedTemp
var deliverAt = new Date();
@ -142,16 +142,14 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
if (minAgo > 12 || minAgo < -5) { // Dexcom data is too old, or way in the future
rT.reason = "If current system time "+systemTime+" is correct, then BG data is too old. The last BG data was read "+minAgo+"m ago at "+bgTime;
// if BG is too old/noisy, or is changing less than 1 mg/dL/5m for 45m, cancel any high temps and shorten any long zero temps
//cherry pick from oref upstream dev cb8e94990301277fb1016c778b4e9efa55a6edbc
} else if ( bg > 60 && glucose_status.delta == 0 && glucose_status.short_avgdelta > -1 && glucose_status.short_avgdelta < 1 && glucose_status.long_avgdelta > -1 && glucose_status.long_avgdelta < 1 && !isSaveCgmSource) {
} else if ( bg > 60 && flatBGsDetected) {
if ( glucose_status.last_cal && glucose_status.last_cal < 3 ) {
rT.reason = "CGM was just calibrated";
} else {
rT.reason = "Error: CGM data is unchanged for the past ~45m";
}
}
//cherry pick from oref upstream dev cb8e94990301277fb1016c778b4e9efa55a6edbc
if (bg <= 10 || bg === 38 || noise >= 3 || minAgo > 12 || minAgo < -5 || ( bg > 60 && glucose_status.delta == 0 && glucose_status.short_avgdelta > -1 && glucose_status.short_avgdelta < 1 && glucose_status.long_avgdelta > -1 && glucose_status.long_avgdelta < 1 ) && !isSaveCgmSource ) {
if (bg <= 10 || bg === 38 || noise >= 3 || minAgo > 12 || minAgo < -5 || ( bg > 60 && flatBGsDetected )) {
if (currenttemp.rate > basal) { // high temp is running
rT.reason += ". Replacing high temp basal of "+currenttemp.rate+" with neutral temp of "+basal;
rT.deliverAt = deliverAt;

View file

@ -110,7 +110,7 @@ function enable_smb(
return false;
}
var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, microBolusAllowed, reservoir_data, currentTime, isSaveCgmSource) {
var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, microBolusAllowed, reservoir_data, currentTime, flatBGsDetected) {
var rT = {}; //short for requestedTemp
var deliverAt = new Date();
@ -142,16 +142,14 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
if (minAgo > 12 || minAgo < -5) { // Dexcom data is too old, or way in the future
rT.reason = "If current system time "+systemTime+" is correct, then BG data is too old. The last BG data was read "+minAgo+"m ago at "+bgTime;
// if BG is too old/noisy, or is changing less than 1 mg/dL/5m for 45m, cancel any high temps and shorten any long zero temps
//cherry pick from oref upstream dev cb8e94990301277fb1016c778b4e9efa55a6edbc
} else if ( bg > 60 && glucose_status.delta == 0 && glucose_status.short_avgdelta > -1 && glucose_status.short_avgdelta < 1 && glucose_status.long_avgdelta > -1 && glucose_status.long_avgdelta < 1 && !isSaveCgmSource) {
} else if ( bg > 60 && flatBGsDetected) {
if ( glucose_status.last_cal && glucose_status.last_cal < 3 ) {
rT.reason = "CGM was just calibrated";
} else {
rT.reason = "Error: CGM data is unchanged for the past ~45m";
}
}
//cherry pick from oref upstream dev cb8e94990301277fb1016c778b4e9efa55a6edbc
if (bg <= 10 || bg === 38 || noise >= 3 || minAgo > 12 || minAgo < -5 || ( bg > 60 && glucose_status.delta == 0 && glucose_status.short_avgdelta > -1 && glucose_status.short_avgdelta < 1 && glucose_status.long_avgdelta > -1 && glucose_status.long_avgdelta < 1 ) && !isSaveCgmSource ) {
if (bg <= 10 || bg === 38 || noise >= 3 || minAgo > 12 || minAgo < -5 || ( bg > 60 && flatBGsDetected )) {
if (currenttemp.rate > basal) { // high temp is running
rT.reason += ". Replacing high temp basal of "+currenttemp.rate+" with neutral temp of "+basal;
rT.deliverAt = deliverAt;

View file

@ -162,7 +162,7 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
microBolusAllowed: Boolean,
uamAllowed: Boolean,
advancedFiltering: Boolean,
isSaveCgmSource: Boolean
flatBGsDetected: Boolean
) {
this.profile = JSONObject()
this.profile.put("max_iob", maxIob)

View file

@ -4,7 +4,6 @@ import android.content.Context
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.core.extensions.target
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.core.utils.MidnightUtils
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.database.ValueWrapper
@ -14,6 +13,7 @@ import info.nightscout.interfaces.aps.AutosensResult
import info.nightscout.interfaces.aps.DetermineBasalAdapter
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.iob.GlucoseStatusProvider
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.plugin.PluginBase

View file

@ -57,7 +57,7 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
private var microBolusAllowed = false
private var smbAlwaysAllowed = false
private var currentTime: Long = 0
private var saveCgmSource = false
private var flatBGsDetected = false
override var currentTempParam: String? = null
override var iobDataParam: String? = null
@ -79,7 +79,7 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
aapsLogger.debug(LTag.APS, "MicroBolusAllowed: $microBolusAllowed")
aapsLogger.debug(LTag.APS, "SMBAlwaysAllowed: $smbAlwaysAllowed")
aapsLogger.debug(LTag.APS, "CurrentTime: $currentTime")
aapsLogger.debug(LTag.APS, "isSaveCgmSource: $saveCgmSource")
aapsLogger.debug(LTag.APS, "flatBGsDetected: $flatBGsDetected")
var determineBasalResultSMB: DetermineBasalResultSMB? = null
val rhino = Context.enter()
val scope: Scriptable = rhino.initStandardObjects()
@ -119,7 +119,7 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
java.lang.Boolean.valueOf(microBolusAllowed),
makeParam(null, rhino, scope), // reservoir data as undefined
java.lang.Long.valueOf(currentTime),
java.lang.Boolean.valueOf(saveCgmSource)
java.lang.Boolean.valueOf(flatBGsDetected)
)
val jsResult = determineBasalObj.call(rhino, scope, scope, params) as NativeObject
scriptDebug = LoggerCallback.scriptDebug
@ -174,7 +174,7 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
microBolusAllowed: Boolean,
uamAllowed: Boolean,
advancedFiltering: Boolean,
isSaveCgmSource: Boolean
flatBGsDetected: Boolean
) {
val pump = activePlugin.activePump
val pumpBolusStep = pump.pumpDescription.bolusStep
@ -262,7 +262,7 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
this.microBolusAllowed = microBolusAllowed
smbAlwaysAllowed = advancedFiltering
currentTime = now
saveCgmSource = isSaveCgmSource
this.flatBGsDetected = flatBGsDetected
}
private fun makeParam(jsonObject: JSONObject?, rhino: Context, scope: Scriptable): Any {

View file

@ -6,7 +6,6 @@ import androidx.preference.SwitchPreference
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.core.extensions.target
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.core.utils.MidnightUtils
import info.nightscout.database.ValueWrapper
import info.nightscout.database.impl.AppRepository
@ -14,8 +13,10 @@ import info.nightscout.interfaces.aps.APS
import info.nightscout.interfaces.aps.AutosensResult
import info.nightscout.interfaces.aps.DetermineBasalAdapter
import info.nightscout.interfaces.aps.SMBDefaults
import info.nightscout.interfaces.bgQualityCheck.BgQualityCheck
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.iob.GlucoseStatusProvider
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.plugin.PluginBase
@ -55,7 +56,8 @@ class OpenAPSSMBPlugin @Inject constructor(
private val sp: SP,
private val dateUtil: DateUtil,
private val repository: AppRepository,
private val glucoseStatusProvider: GlucoseStatusProvider
private val glucoseStatusProvider: GlucoseStatusProvider,
private val bgQualityCheck: BgQualityCheck
) : PluginBase(
PluginDescription()
.mainType(PluginType.APS)
@ -191,6 +193,7 @@ class OpenAPSSMBPlugin @Inject constructor(
constraintChecker.isUAMEnabled(it)
inputConstraints.copyReasons(it)
}
val flatBGsDetected = bgQualityCheck.state == BgQualityCheck.State.FLAT
profiler.log(LTag.APS, "detectSensitivityAndCarbAbsorption()", startPart)
profiler.log(LTag.APS, "SMB data gathering", start)
start = System.currentTimeMillis()
@ -207,7 +210,7 @@ class OpenAPSSMBPlugin @Inject constructor(
smbAllowed.value(),
uam.value(),
advancedFiltering.value(),
activePlugin.activeBgSource.javaClass.simpleName == "DexcomPlugin"
flatBGsDetected
)
val now = System.currentTimeMillis()
val determineBasalResultSMB = determineBasalAdapterSMBJS.invoke()

View file

@ -59,7 +59,7 @@ class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scri
private var microBolusAllowed = false
private var smbAlwaysAllowed = false
private var currentTime: Long = 0
private var saveCgmSource = false
private var flatBGsDetected = false
override var currentTempParam: String? = null
override var iobDataParam: String? = null
@ -81,7 +81,7 @@ class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scri
aapsLogger.debug(LTag.APS, "MicroBolusAllowed: $microBolusAllowed")
aapsLogger.debug(LTag.APS, "SMBAlwaysAllowed: $smbAlwaysAllowed")
aapsLogger.debug(LTag.APS, "CurrentTime: $currentTime")
aapsLogger.debug(LTag.APS, "isSaveCgmSource: $saveCgmSource")
aapsLogger.debug(LTag.APS, "flatBGsDetected: $flatBGsDetected")
var determineBasalResultSMB: DetermineBasalResultSMB? = null
val rhino = Context.enter()
val scope: Scriptable = rhino.initStandardObjects()
@ -121,7 +121,7 @@ class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scri
java.lang.Boolean.valueOf(microBolusAllowed),
makeParam(null, rhino, scope), // reservoir data as undefined
java.lang.Long.valueOf(currentTime),
java.lang.Boolean.valueOf(saveCgmSource)
java.lang.Boolean.valueOf(flatBGsDetected)
)
val jsResult = determineBasalObj.call(rhino, scope, scope, params) as NativeObject
scriptDebug = LoggerCallback.scriptDebug
@ -176,7 +176,7 @@ class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scri
microBolusAllowed: Boolean,
uamAllowed: Boolean,
advancedFiltering: Boolean,
isSaveCgmSource: Boolean
flatBGsDetected: Boolean
) {
val pump = activePlugin.activePump
val pumpBolusStep = pump.pumpDescription.bolusStep
@ -312,7 +312,7 @@ class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scri
this.microBolusAllowed = microBolusAllowed
smbAlwaysAllowed = advancedFiltering
currentTime = now
saveCgmSource = isSaveCgmSource
this.flatBGsDetected = flatBGsDetected
}
private fun makeParam(jsonObject: JSONObject?, rhino: Context, scope: Scriptable): Any {

View file

@ -3,11 +3,12 @@ package info.nightscout.plugins.aps.openAPSSMBDynamicISF
import android.content.Context
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.database.impl.AppRepository
import info.nightscout.interfaces.Config
import info.nightscout.interfaces.aps.DetermineBasalAdapter
import info.nightscout.interfaces.bgQualityCheck.BgQualityCheck
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.iob.GlucoseStatusProvider
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.profile.ProfileFunction
@ -42,7 +43,8 @@ class OpenAPSSMBDynamicISFPlugin @Inject constructor(
dateUtil: DateUtil,
repository: AppRepository,
glucoseStatusProvider: GlucoseStatusProvider,
private val config: Config
private val config: Config,
private val bgQualityCheck: BgQualityCheck
) : OpenAPSSMBPlugin(
injector,
aapsLogger,
@ -58,7 +60,8 @@ class OpenAPSSMBDynamicISFPlugin @Inject constructor(
sp,
dateUtil,
repository,
glucoseStatusProvider
glucoseStatusProvider,
bgQualityCheck
) {
init {

View file

@ -90,12 +90,28 @@
<!-- Autotune -->
<string name="insulin_peak">Пик</string>
<!-- Loop -->
<string name="run_now">Выполнить сейчас</string>
<string name="smb_frequency_exceeded">Болюс был подан в течение последних трех минут, минуя супер микро болюс SMB</string>
<string name="pump_not_initialized">Помпа не инициализирована!</string>
<string name="loop_shortname">ЗЦ</string>
<string name="description_loop">Активировать или деактивировать запуск цикла.</string>
<string name="no_aps_selected">APS не выбрана или не выдала результат</string>
<string name="ignore5m">Игнорировать 5 м</string>
<string name="ignore15m">Игнорировать 15 м</string>
<string name="ignore30m">Игнорировать 30 м</string>
<string name="carbs_suggestion">Предложены углеводы</string>
<string name="open_loop_new_suggestion">Есть новое предложение</string>
<string name="basal_set_correctly">Базал задан корректно</string>
<string name="last_run_label">Предыдущее выполнение</string>
<string name="loop_aps_label">Система ИПЖ</string>
<string name="request_label">Запрос</string>
<string name="loop_constraints_processed_label">После наложенных ограничений</string>
<string name="loop_tbr_request_time_label">Время запроса временной базальной скорости</string>
<string name="loop_tbr_execution_time_label">Время выполнения временной базальной скорости</string>
<string name="loop_tbr_set_by_pump_label">Временный базал задан помпой</string>
<string name="loop_smb_request_time_label">Время запроса микроболюса SMB</string>
<string name="loop_smb_execution_time_label">Время выполнения микроболюса SMB</string>
<string name="loop_smb_set_by_pump_label">Супер микро болюс SMB задан помпой</string>
<string name="loop_open_mode_min_change">Минимальный запрос на изменения [%]</string>
<string name="loop_open_mode_min_change_summary" formatted="false">Алгоритм ИПЖ выдаст всплывающее окно с запросом на новые изменения, только если изменение больше, чем это значение в %. Значение по умолчанию — 20%</string>
</resources>

View file

@ -27,6 +27,8 @@ dependencies {
implementation project(':app-wear-shared:shared')
implementation project(':core:interfaces')
testImplementation project(':implementation')
api "androidx.constraintlayout:constraintlayout:$constraintlayout_version"
api "com.google.android.gms:play-services-location:$play_services_location_version"
}

View file

@ -9,12 +9,12 @@ import info.nightscout.automation.elements.InputString
import info.nightscout.automation.elements.LabelWithElement
import info.nightscout.automation.elements.LayoutBuilder
import info.nightscout.core.extensions.fromConstant
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.database.entities.TherapyEvent
import info.nightscout.database.entities.UserEntry
import info.nightscout.database.entities.ValueWithUnit
import info.nightscout.database.impl.AppRepository
import info.nightscout.database.impl.transactions.InsertIfNewByTimestampTherapyEventTransaction
import info.nightscout.interfaces.iob.GlucoseStatusProvider
import info.nightscout.interfaces.logging.UserEntryLogger
import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.interfaces.queue.Callback

View file

@ -15,8 +15,8 @@ import info.nightscout.automation.events.EventTriggerChanged
import info.nightscout.automation.events.EventTriggerClone
import info.nightscout.automation.events.EventTriggerRemove
import info.nightscout.automation.services.LastLocationDataContainer
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.database.impl.AppRepository
import info.nightscout.interfaces.iob.GlucoseStatusProvider
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.profile.ProfileFunction

View file

@ -7,7 +7,7 @@ import info.nightscout.androidaps.TestBaseWithProfile
import info.nightscout.androidaps.TestPumpPlugin
import info.nightscout.automation.AutomationPlugin
import info.nightscout.automation.services.LastLocationDataContainer
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.implementation.iob.GlucoseStatusProviderImpl
import info.nightscout.interfaces.aps.AutosensDataStore
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.plugin.ActivePlugin
@ -51,7 +51,7 @@ open class TriggerTestBase : TestBaseWithProfile() {
it.repository = repository
it.activePlugin = activePlugin
it.iobCobCalculator = iobCobCalculator
it.glucoseStatusProvider = GlucoseStatusProvider(aapsLogger, iobCobCalculator, dateUtil)
it.glucoseStatusProvider = GlucoseStatusProviderImpl(aapsLogger, iobCobCalculator, dateUtil)
it.dateUtil = dateUtil
}
if (it is TriggerBg) {

View file

@ -28,6 +28,8 @@ dependencies {
implementation project(':core:utils')
implementation project(':core:validators')
testImplementation project(':implementation')
api "androidx.appcompat:appcompat:$appcompat_version"
api "com.google.android.material:material:$material_version"

View file

@ -7,13 +7,13 @@ import android.os.Bundle
import dagger.android.HasAndroidInjector
import info.nightscout.core.extensions.durationInMinutes
import info.nightscout.core.extensions.toStringFull
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.core.iob.round
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.interfaces.receivers.ReceiverStatusStore
import info.nightscout.interfaces.Config
import info.nightscout.interfaces.aps.Loop
import info.nightscout.interfaces.iob.GlucoseStatusProvider
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.nsclient.ProcessedDeviceStatusData
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginDescription
@ -21,8 +21,8 @@ import info.nightscout.interfaces.plugin.PluginType
import info.nightscout.interfaces.profile.DefaultValueHelper
import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.interfaces.receivers.Intents
import info.nightscout.interfaces.receivers.ReceiverStatusStore
import info.nightscout.plugins.R
import info.nightscout.interfaces.nsclient.ProcessedDeviceStatusData
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.Event

View file

@ -31,7 +31,6 @@ import info.nightscout.core.extensions.directionToIcon
import info.nightscout.core.extensions.valueToUnitsString
import info.nightscout.core.graph.OverviewData
import info.nightscout.core.iob.displayText
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.core.profile.ProfileSealed
import info.nightscout.core.ui.UIRunnable
import info.nightscout.core.ui.dialogs.OKDialog
@ -52,6 +51,7 @@ import info.nightscout.interfaces.automation.Automation
import info.nightscout.interfaces.bgQualityCheck.BgQualityCheck
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.iob.GlucoseStatusProvider
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.logging.UserEntryLogger
import info.nightscout.interfaces.nsclient.NSSettingsStatus

View file

@ -10,11 +10,11 @@ import dagger.android.HasAndroidInjector
import info.nightscout.core.extensions.toStringShort
import info.nightscout.core.extensions.valueToUnitsString
import info.nightscout.core.iob.generateCOBString
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.core.iob.round
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.NotificationHolder
import info.nightscout.interfaces.iob.GlucoseStatusProvider
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.plugin.PluginBase

View file

@ -16,7 +16,7 @@ import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T
import javax.inject.Inject
class AuthRequest internal constructor(
class AuthRequest(
injector: HasAndroidInjector,
var requester: Sms,
requestText: String,

View file

@ -14,7 +14,6 @@ import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.core.events.EventNewNotification
import info.nightscout.core.extensions.valueToUnitsString
import info.nightscout.core.iob.generateCOBString
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.core.iob.round
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.core.utils.receivers.DataWorkerStorage
@ -38,6 +37,7 @@ import info.nightscout.interfaces.XDripBroadcast
import info.nightscout.interfaces.aps.Loop
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.iob.GlucoseStatusProvider
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.logging.UserEntryLogger
import info.nightscout.interfaces.notifications.Notification

View file

@ -9,7 +9,6 @@ import info.nightscout.core.extensions.valueToUnits
import info.nightscout.core.extensions.valueToUnitsString
import info.nightscout.core.graph.data.GlucoseValueDataPoint
import info.nightscout.core.iob.generateCOBString
import info.nightscout.core.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.core.iob.round
import info.nightscout.core.ui.toast.ToastUtils
import info.nightscout.core.utils.fabric.FabricPrivacy
@ -34,6 +33,7 @@ import info.nightscout.interfaces.GlucoseUnit
import info.nightscout.interfaces.aps.Loop
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.iob.GlucoseStatusProvider
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.logging.UserEntryLogger
import info.nightscout.interfaces.nsclient.ProcessedDeviceStatusData

View file

@ -10,7 +10,6 @@ import info.nightscout.core.graph.OverviewData
import info.nightscout.core.iob.combine
import info.nightscout.core.iob.copy
import info.nightscout.core.iob.determineBasalJson
import info.nightscout.core.iob.iobCobCalculator.AutosensDataStoreObject
import info.nightscout.core.iob.plus
import info.nightscout.core.iob.round
import info.nightscout.core.utils.fabric.FabricPrivacy
@ -39,6 +38,7 @@ import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.interfaces.utils.DecimalFormatter
import info.nightscout.interfaces.utils.MidnightTime
import info.nightscout.plugins.R
import info.nightscout.plugins.iob.iobCobCalculator.data.AutosensDataStoreObject
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.Event

View file

@ -1,4 +1,4 @@
package info.nightscout.core.iob.iobCobCalculator
package info.nightscout.plugins.iob.iobCobCalculator.data
import androidx.collection.LongSparseArray
import androidx.collection.size
@ -310,7 +310,9 @@ class AutosensDataStoreObject : AutosensDataStore {
val previous = bData[i + 1]
val mSecDiff = current.timestamp - previous.timestamp
val adjusted = (mSecDiff - T.mins(5).msecs()) / 1000
aapsLogger.debug(LTag.AUTOSENS) { "Adjusting bucketed data time. Current: ${dateUtil.dateAndTimeAndSecondsString(current.timestamp)} to: ${dateUtil.dateAndTimeAndSecondsString(previous.timestamp + T.mins(5).msecs())} by $adjusted sec" }
aapsLogger.debug(LTag.AUTOSENS) { "Adjusting bucketed data time. Current: ${dateUtil.dateAndTimeAndSecondsString(current.timestamp)} to: ${dateUtil.dateAndTimeAndSecondsString(previous.timestamp + T.mins(
5
).msecs())} by $adjusted sec" }
if (abs(adjusted) > 90) {
// too big adjustment, fallback to non 5 min data
aapsLogger.debug(LTag.AUTOSENS, "Fallback to non 5 min data")
@ -337,4 +339,4 @@ class AutosensDataStoreObject : AutosensDataStore {
}
return if (count != 0) sum / count else 0.0
}
}
}

View file

@ -179,7 +179,6 @@ open class VirtualPumpPlugin @Inject constructor(
val result = PumpEnactResult(injector)
.success(true)
.bolusDelivered(detailedBolusInfo.insulin)
.carbsDelivered(detailedBolusInfo.carbs)
.enacted(detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0)
.comment(rh.gs(info.nightscout.core.ui.R.string.virtualpump_resultok))
val bolusingEvent = EventOverviewBolusProgress

View file

@ -41,17 +41,11 @@ class AidexPlugin @Inject constructor(
aapsLogger, rh, injector
), BgSource {
private var advancedFiltering = false
/**
* Aidex App doesn't have upload to NS
*/
override fun shouldUploadToNs(glucoseValue: GlucoseValue): Boolean = true
override fun advancedFilteringSupported(): Boolean {
return advancedFiltering
}
// Allow only for pumpcontrol or dev & engineering_mode
override fun specialEnableCondition(): Boolean {
return config.APS.not() || config.isDev() && config.isEngineeringMode()

View file

@ -27,6 +27,7 @@ import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType
import info.nightscout.interfaces.profile.Profile
import info.nightscout.interfaces.source.BgSource
import info.nightscout.interfaces.source.DexcomBoyda
import info.nightscout.plugins.R
import info.nightscout.plugins.source.activities.RequestDexcomPermissionActivity
import info.nightscout.rx.logging.AAPSLogger
@ -58,7 +59,7 @@ class DexcomPlugin @Inject constructor(
.preferencesId(R.xml.pref_dexcom)
.description(R.string.description_source_dexcom),
aapsLogger, rh, injector
), BgSource {
), BgSource, DexcomBoyda {
init {
if (!config.NSCLIENT) {
@ -66,9 +67,7 @@ class DexcomPlugin @Inject constructor(
}
}
override fun advancedFilteringSupported(): Boolean {
return true
}
override fun advancedFilteringSupported(): Boolean = true
override fun shouldUploadToNs(glucoseValue: GlucoseValue): Boolean =
(glucoseValue.sourceSensor == GlucoseValue.SourceSensor.DEXCOM_G6_NATIVE ||

View file

@ -64,9 +64,7 @@ class NSClientSourcePlugin @Inject constructor(
}
}
override fun advancedFilteringSupported(): Boolean {
return isAdvancedFilteringEnabled
}
override fun advancedFilteringSupported(): Boolean = isAdvancedFilteringEnabled
override fun shouldUploadToNs(glucoseValue: GlucoseValue): Boolean = false

View file

@ -73,9 +73,7 @@ class RandomBgPlugin @Inject constructor(
private val disposable = CompositeDisposable()
override fun advancedFilteringSupported(): Boolean {
return true
}
override fun advancedFilteringSupported(): Boolean = true
override fun shouldUploadToNs(glucoseValue: GlucoseValue): Boolean =
glucoseValue.sourceSensor == GlucoseValue.SourceSensor.RANDOM && sp.getBoolean(info.nightscout.core.utils.R.string.key_do_ns_upload, false)

View file

@ -43,9 +43,7 @@ class XdripPlugin @Inject constructor(
override fun shouldUploadToNs(glucoseValue: GlucoseValue): Boolean = false
override fun advancedFilteringSupported(): Boolean {
return advancedFiltering
}
override fun advancedFilteringSupported(): Boolean = advancedFiltering
private fun detectSource(glucoseValue: GlucoseValue) {
advancedFiltering = arrayOf(

File diff suppressed because it is too large Load diff

View file

@ -7,9 +7,11 @@ import info.nightscout.interfaces.bgQualityCheck.BgQualityCheck
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.plugin.PluginBase
import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType
import info.nightscout.interfaces.source.DexcomBoyda
import info.nightscout.plugins.support.R
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
@ -24,6 +26,7 @@ import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@Singleton
@ -33,10 +36,11 @@ class BgQualityCheckPlugin @Inject constructor(
rh: ResourceHelper,
private val rxBus: RxBus,
private val iobCobCalculator: IobCobCalculator,
private val aapsSchedulers:
private val aapsSchedulers:
AapsSchedulers,
private val fabricPrivacy: FabricPrivacy,
private val dateUtil: DateUtil
private val dateUtil: DateUtil,
private val activePlugin: ActivePlugin
) : PluginBase(
PluginDescription()
.mainType(PluginType.CONSTRAINTS)
@ -49,13 +53,6 @@ class BgQualityCheckPlugin @Inject constructor(
private var disposable: CompositeDisposable = CompositeDisposable()
enum class State {
UNKNOWN,
FIVE_MIN_DATA,
RECALCULATED,
DOUBLED
}
override fun onStart() {
super.onStart()
disposable += rxBus
@ -69,51 +66,87 @@ class BgQualityCheckPlugin @Inject constructor(
disposable.clear()
}
var state: State = State.UNKNOWN
override var state: BgQualityCheck.State = BgQualityCheck.State.UNKNOWN
override var message: String = ""
// Fallback to LGS if BG values are doubled
override fun applyMaxIOBConstraints(maxIob: Constraint<Double>): Constraint<Double> =
if (state == State.DOUBLED)
if (state == BgQualityCheck.State.DOUBLED)
maxIob.set(aapsLogger, 0.0, "Doubled values in BGSource", this)
else
maxIob
@Suppress("CascadeIf") fun processBgData() {
fun processBgData() {
val readings = iobCobCalculator.ads.getBgReadingsDataTableCopy()
for (i in readings.indices)
// Deltas are calculated from last ~50 min. Detect RED state only on this interval
if (i < min(readings.size - 2, 10))
if (abs(readings[i].timestamp - readings[i + 1].timestamp) <= T.secs(20).msecs()) {
state = State.DOUBLED
state = BgQualityCheck.State.DOUBLED
aapsLogger.debug(LTag.CORE, "BG similar. Turning on red state.\n${readings[i]}\n${readings[i + 1]}")
message = rh.gs(R.string.bg_too_close, dateUtil.dateAndTimeAndSecondsString(readings[i].timestamp), dateUtil.dateAndTimeAndSecondsString(readings[i + 1].timestamp))
return
}
if (iobCobCalculator.ads.lastUsed5minCalculation == true) {
state = State.FIVE_MIN_DATA
if (activePlugin.activeBgSource !is DexcomBoyda && isBgFlatForInterval(staleBgCheckPeriodMinutes, staleBgMaxDeltaMgdl) == true) {
state = BgQualityCheck.State.FLAT
message = rh.gs(R.string.a11y_bg_quality_flat)
} else if (iobCobCalculator.ads.lastUsed5minCalculation == true) {
state = BgQualityCheck.State.FIVE_MIN_DATA
message = "Data is clean"
} else if (iobCobCalculator.ads.lastUsed5minCalculation == false) {
state = State.RECALCULATED
state = BgQualityCheck.State.RECALCULATED
message = rh.gs(R.string.recalculated_data_used)
} else {
state = State.UNKNOWN
state = BgQualityCheck.State.UNKNOWN
message = ""
}
}
// inspired by @justmara
@Suppress("SpellCheckingInspection", "SameParameterValue")
private fun isBgFlatForInterval(minutes: Long, maxDelta: Double): Boolean? {
val data = iobCobCalculator.ads.getBgReadingsDataTableCopy()
val lastBg = iobCobCalculator.ads.lastBg()?.value
val now = dateUtil.now()
val offset = now - T.mins(minutes).msecs()
val sizeRecords = data.size
lastBg ?: return null
if (sizeRecords < 5) return null // not enough data
if (data[data.size - 1].timestamp > now - 45 * 60 * 1000L) return null // data too fresh to detect
if (data[0].timestamp < now - 7 * 60 * 1000L) return null // data is old
var bgmin: Double = lastBg
var bgmax: Double = bgmin
for (bg in data) {
if (bg.timestamp < offset) break
bgmin = min(bgmin, bg.value)
bgmax = max(bgmax, bg.value)
if (bgmax - bgmin > maxDelta) return false
}
return true
}
@DrawableRes override fun icon(): Int =
when (state) {
State.UNKNOWN -> 0
State.FIVE_MIN_DATA -> 0
State.RECALCULATED -> R.drawable.ic_baseline_warning_24_yellow
State.DOUBLED -> R.drawable.ic_baseline_warning_24_red
BgQualityCheck.State.UNKNOWN -> 0
BgQualityCheck.State.FIVE_MIN_DATA -> 0
BgQualityCheck.State.RECALCULATED -> R.drawable.ic_baseline_warning_24_yellow
BgQualityCheck.State.DOUBLED -> R.drawable.ic_baseline_warning_24_red
BgQualityCheck.State.FLAT -> R.drawable.ic_baseline_trending_flat_24
}
override fun stateDescription(): String =
when (state) {
State.RECALCULATED -> rh.gs(R.string.a11y_bg_quality_recalculated)
State.DOUBLED -> rh.gs(R.string.a11y_bg_quality_doubles)
else -> ""
BgQualityCheck.State.RECALCULATED -> rh.gs(R.string.a11y_bg_quality_recalculated)
BgQualityCheck.State.DOUBLED -> rh.gs(R.string.a11y_bg_quality_doubles)
BgQualityCheck.State.FLAT -> rh.gs(R.string.a11y_bg_quality_flat)
else -> ""
}
companion object {
const val staleBgCheckPeriodMinutes = 45L
const val staleBgMaxDeltaMgdl = 2.0
}
}

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FF0000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M22,12l-4,-4v3H3v2h15v3z"/>
</vector>

View file

@ -20,4 +20,5 @@
<string name="bg_too_close">BS for nær:\n%1$s\n%2$s</string>
<string name="a11y_bg_quality_recalculated">beregnet på nytt</string>
<string name="a11y_bg_quality_doubles">doble registreringer</string>
<string name="a11y_bg_quality_flat">Flate verdier. Vurderer dette til å være feil</string>
</resources>

View file

@ -5,6 +5,7 @@
<string name="dst_in_24h_warning">Переход на летнее/зимнее время через 24 часа или менее</string>
<string name="dst_loop_disabled_warning">Изменение сезонного времени произошло меньше 3 часов назад-Закрытый цикл выключен</string>
<!-- Storage constraint -->
<string name="disk_full">Освободите по крайней мере %1$d MB из внутренней памяти! Цикл остановлен!</string>
<!-- Version Checker -->
<string name="old_version">старая версия</string>
<string name="very_old_version">очень старая версия</string>

View file

@ -39,5 +39,6 @@
<string name="bg_too_close">BG too close:\n%1$s\n%2$s</string>
<string name="a11y_bg_quality_recalculated">recalculated</string>
<string name="a11y_bg_quality_doubles">double entries</string>
<string name="a11y_bg_quality_flat">Flat data. Considered to be wrong</string>
</resources>

View file

@ -6,14 +6,18 @@ import info.nightscout.androidaps.TestBase
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.database.entities.GlucoseValue
import info.nightscout.interfaces.aps.AutosensDataStore
import info.nightscout.interfaces.bgQualityCheck.BgQualityCheck
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.source.BgSource
import info.nightscout.interfaces.source.DexcomBoyda
import info.nightscout.plugins.support.R
import info.nightscout.rx.bus.RxBus
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T
import org.junit.Assert
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.ArgumentMatchers.any
@ -28,10 +32,12 @@ class BgQualityCheckPluginTest : TestBase() {
@Mock lateinit var fabricPrivacy: FabricPrivacy
@Mock lateinit var dateUtil: DateUtil
@Mock lateinit var autosensDataStore: AutosensDataStore
@Mock lateinit var activePlugin: ActivePlugin
private lateinit var plugin: BgQualityCheckPlugin
val injector = HasAndroidInjector { AndroidInjector { } }
private val injector = HasAndroidInjector { AndroidInjector { } }
val now = 100000000L
//private val autosensDataStore = AutosensDataStoreObject()
@BeforeEach
@ -45,27 +51,29 @@ class BgQualityCheckPluginTest : TestBase() {
iobCobCalculator,
aapsSchedulers,
fabricPrivacy,
dateUtil
dateUtil,
activePlugin
)
`when`(iobCobCalculator.ads).thenReturn(autosensDataStore)
`when`(rh.gs(anyInt())).thenReturn("")
`when`(rh.gs(anyInt(), any(), any())).thenReturn("")
`when`(dateUtil.now()).thenReturn(now)
}
@Test
fun runTest() {
`when`(autosensDataStore.lastUsed5minCalculation).thenReturn(null)
plugin.processBgData()
Assert.assertEquals(BgQualityCheckPlugin.State.UNKNOWN, plugin.state)
Assert.assertEquals(0, plugin.icon())
Assertions.assertEquals(BgQualityCheck.State.UNKNOWN, plugin.state)
Assertions.assertEquals(0, plugin.icon())
`when`(autosensDataStore.lastUsed5minCalculation).thenReturn(true)
plugin.processBgData()
Assert.assertEquals(BgQualityCheckPlugin.State.FIVE_MIN_DATA, plugin.state)
Assert.assertEquals(0, plugin.icon())
Assertions.assertEquals(BgQualityCheck.State.FIVE_MIN_DATA, plugin.state)
Assertions.assertEquals(0, plugin.icon())
`when`(autosensDataStore.lastUsed5minCalculation).thenReturn(false)
plugin.processBgData()
Assert.assertEquals(BgQualityCheckPlugin.State.RECALCULATED, plugin.state)
Assert.assertEquals(R.drawable.ic_baseline_warning_24_yellow, plugin.icon())
Assertions.assertEquals(BgQualityCheck.State.RECALCULATED, plugin.state)
Assertions.assertEquals(R.drawable.ic_baseline_warning_24_yellow, plugin.icon())
val superData: MutableList<GlucoseValue> = ArrayList()
superData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = T.mins(20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
@ -76,10 +84,10 @@ class BgQualityCheckPluginTest : TestBase() {
`when`(autosensDataStore.lastUsed5minCalculation).thenReturn(true)
plugin.processBgData()
Assert.assertEquals(BgQualityCheckPlugin.State.FIVE_MIN_DATA, plugin.state)
Assertions.assertEquals(BgQualityCheck.State.FIVE_MIN_DATA, plugin.state)
`when`(autosensDataStore.lastUsed5minCalculation).thenReturn(false)
plugin.processBgData()
Assert.assertEquals(BgQualityCheckPlugin.State.RECALCULATED, plugin.state)
Assertions.assertEquals(BgQualityCheck.State.RECALCULATED, plugin.state)
val duplicatedData: MutableList<GlucoseValue> = ArrayList()
duplicatedData.add(
@ -136,8 +144,8 @@ class BgQualityCheckPluginTest : TestBase() {
`when`(autosensDataStore.lastUsed5minCalculation).thenReturn(true)
plugin.processBgData()
Assert.assertEquals(BgQualityCheckPlugin.State.DOUBLED, plugin.state)
Assert.assertEquals(R.drawable.ic_baseline_warning_24_red, plugin.icon())
Assertions.assertEquals(BgQualityCheck.State.DOUBLED, plugin.state)
Assertions.assertEquals(R.drawable.ic_baseline_warning_24_red, plugin.icon())
val identicalData: MutableList<GlucoseValue> = ArrayList()
identicalData.add(
@ -194,19 +202,73 @@ class BgQualityCheckPluginTest : TestBase() {
`when`(autosensDataStore.lastUsed5minCalculation).thenReturn(false)
plugin.processBgData()
Assert.assertEquals(BgQualityCheckPlugin.State.DOUBLED, plugin.state)
Assertions.assertEquals(BgQualityCheck.State.DOUBLED, plugin.state)
// Flat data
val flatData: MutableList<GlucoseValue> = ArrayList()
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(0).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow
.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 101.0, timestamp = now + T.mins(-10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-25).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 99.0, timestamp = now + T.mins(-30).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-35).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-40).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-45).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(flatData)
`when`(iobCobCalculator.ads.lastBg()).thenReturn(flatData[0])
// Test non-dexcom plugin on flat data
class OtherPlugin : BgSource {
override fun shouldUploadToNs(glucoseValue: GlucoseValue): Boolean = true
}
`when`(activePlugin.activeBgSource).thenReturn(OtherPlugin())
plugin.processBgData()
Assertions.assertEquals(BgQualityCheck.State.FLAT, plugin.state)
Assertions.assertEquals(R.drawable.ic_baseline_trending_flat_24, plugin.icon())
// Test dexcom plugin on flat data
class DexcomPlugin : BgSource, DexcomBoyda {
override fun shouldUploadToNs(glucoseValue: GlucoseValue): Boolean = true
}
`when`(activePlugin.activeBgSource).thenReturn(DexcomPlugin())
plugin.processBgData()
Assertions.assertNotEquals(BgQualityCheck.State.FLAT, plugin.state)
// not enough data
val incompleteData: MutableList<GlucoseValue> = ArrayList()
incompleteData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(0).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
incompleteData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-5).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
`when`(autosensDataStore.getBgReadingsDataTableCopy()).thenReturn(incompleteData)
`when`(iobCobCalculator.ads.lastBg()).thenReturn(incompleteData[0])
`when`(activePlugin.activeBgSource).thenReturn(OtherPlugin())
plugin.processBgData()// must be more than 5 values
Assertions.assertNotEquals(BgQualityCheck.State.FLAT, plugin.state)
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 101.0, timestamp = now + T.mins(-10).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-15).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-20).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-25).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 99.0, timestamp = now + T.mins(-30).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-35).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
flatData.add(GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = now + T.mins(-40).msecs(), sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT))
plugin.processBgData() // must be at least 45 min old
Assertions.assertNotEquals(BgQualityCheck.State.FLAT, plugin.state)
}
@Test
fun applyMaxIOBConstraintsTest() {
plugin.state = BgQualityCheckPlugin.State.UNKNOWN
Assert.assertEquals(10.0, plugin.applyMaxIOBConstraints(Constraint(10.0)).value(), 0.001)
plugin.state = BgQualityCheckPlugin.State.FIVE_MIN_DATA
Assert.assertEquals(10.0, plugin.applyMaxIOBConstraints(Constraint(10.0)).value(), 0.001)
plugin.state = BgQualityCheckPlugin.State.RECALCULATED
Assert.assertEquals(10.0, plugin.applyMaxIOBConstraints(Constraint(10.0)).value(), 0.001)
plugin.state = BgQualityCheckPlugin.State.DOUBLED
Assert.assertEquals(0.0, plugin.applyMaxIOBConstraints(Constraint(10.0)).value(), 0.001)
plugin.state = BgQualityCheck.State.UNKNOWN
Assertions.assertEquals(10.0, plugin.applyMaxIOBConstraints(Constraint(10.0)).value(), 0.001)
plugin.state = BgQualityCheck.State.FIVE_MIN_DATA
Assertions.assertEquals(10.0, plugin.applyMaxIOBConstraints(Constraint(10.0)).value(), 0.001)
plugin.state = BgQualityCheck.State.RECALCULATED
Assertions.assertEquals(10.0, plugin.applyMaxIOBConstraints(Constraint(10.0)).value(), 0.001)
plugin.state = BgQualityCheck.State.DOUBLED
Assertions.assertEquals(0.0, plugin.applyMaxIOBConstraints(Constraint(10.0)).value(), 0.001)
}
}

View file

@ -28,7 +28,7 @@ dependencies {
// NSClient, Tidepool
api("io.socket:socket.io-client:1.0.0") {
api("io.socket:socket.io-client:1.0.2") {
// excluding org.json which is provided by Android
exclude group: "org.json", module: "json"
}

View file

@ -202,7 +202,7 @@ class StoreDataForDbImpl @Inject constructor(
SystemClock.sleep(pause)
if (carbs.isNotEmpty())
repository.runTransactionForResult(SyncNsCarbsTransaction(carbs))
repository.runTransactionForResult(SyncNsCarbsTransaction(carbs, config.NSCLIENT))
.doOnError {
aapsLogger.error(LTag.DATABASE, "Error while saving carbs", it)
}
@ -259,7 +259,7 @@ class StoreDataForDbImpl @Inject constructor(
SystemClock.sleep(pause)
if (temporaryTargets.isNotEmpty())
repository.runTransactionForResult(SyncNsTemporaryTargetTransaction(temporaryTargets))
repository.runTransactionForResult(SyncNsTemporaryTargetTransaction(temporaryTargets, config.NSCLIENT))
.doOnError {
aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it)
}
@ -334,7 +334,7 @@ class StoreDataForDbImpl @Inject constructor(
SystemClock.sleep(pause)
if (temporaryBasals.isNotEmpty())
repository.runTransactionForResult(SyncNsTemporaryBasalTransaction(temporaryBasals))
repository.runTransactionForResult(SyncNsTemporaryBasalTransaction(temporaryBasals, config.NSCLIENT))
.doOnError {
aapsLogger.error(LTag.DATABASE, "Error while saving temporary basal", it)
}
@ -527,7 +527,7 @@ class StoreDataForDbImpl @Inject constructor(
}
}
if (therapyEvents.isNotEmpty())
repository.runTransactionForResult(SyncNsTherapyEventTransaction(therapyEvents))
repository.runTransactionForResult(SyncNsTherapyEventTransaction(therapyEvents, config.NSCLIENT))
.doOnError {
aapsLogger.error(LTag.DATABASE, "Error while saving therapy event", it)
}
@ -585,7 +585,7 @@ class StoreDataForDbImpl @Inject constructor(
SystemClock.sleep(pause)
if (offlineEvents.isNotEmpty())
repository.runTransactionForResult(SyncNsOfflineEventTransaction(offlineEvents))
repository.runTransactionForResult(SyncNsOfflineEventTransaction(offlineEvents, config.NSCLIENT))
.doOnError {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
}
@ -653,7 +653,7 @@ class StoreDataForDbImpl @Inject constructor(
SystemClock.sleep(pause)
if (extendedBoluses.isNotEmpty())
repository.runTransactionForResult(SyncNsExtendedBolusTransaction(extendedBoluses))
repository.runTransactionForResult(SyncNsExtendedBolusTransaction(extendedBoluses, config.NSCLIENT))
.doOnError {
aapsLogger.error(LTag.DATABASE, "Error while saving extended bolus", it)
}

View file

@ -14,6 +14,5 @@ fun PumpEnactResult.log(): String {
" IsPercent: " + isPercent +
" IsTempCancel: " + isTempCancel +
" bolusDelivered: " + bolusDelivered +
" carbsDelivered: " + carbsDelivered +
" Queued: " + queued
}

View file

@ -629,8 +629,7 @@ public class ComboPlugin extends PumpPluginBase implements Pump, Constraints {
return new PumpEnactResult(getInjector())
.success(true)
.enacted(lastPumpBolus.amount > 0)
.bolusDelivered(lastPumpBolus.amount)
.carbsDelivered(detailedBolusInfo.carbs);
.bolusDelivered(lastPumpBolus.amount);
} finally {
pump.activity = null;
rxBus.send(new EventComboPumpUpdateGUI());

View file

@ -16,6 +16,7 @@ dependencies {
implementation project(':core:utils')
implementation project(':app-wear-shared:shared')
implementation(project(":pump:combov2:comboctl"))
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:$kotlinx_datetime_version")
// This is necessary to avoid errors like these which otherwise come up often at runtime:
// "WARNING: Failed to transform class kotlinx/datetime/TimeZone$Companion

View file

@ -1,7 +1,14 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
plugins {
id 'com.android.library'
id 'kotlin-android'
id 'kotlin-kapt'
id 'com.hiya.jacoco-android'
}
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/test_dependencies.gradle"
apply from: "${project.rootDir}/core/main/jacoco_global.gradle"
android {
namespace 'info.nightscout.comboctl'
@ -10,6 +17,9 @@ android {
kotlin.srcDirs += ['src/commonMain/kotlin', 'src/androidMain/kotlin']
manifest.srcFile 'src/androidMain/AndroidManifest.xml'
}
test {
kotlin.srcDirs += ['src/jvmTest/kotlin']
}
}
}
@ -18,4 +28,8 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-datetime:$kotlinx_datetime_version"
implementation "androidx.core:core-ktx:$core_version"
}
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5'
testImplementation "io.kotlintest:kotlintest-runner-junit5:3.4.2"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit_jupiter_version"
}

View file

@ -1166,6 +1166,19 @@ class Pump(
*/
val setTbrProgressFlow = setTbrProgressReporter.progressFlow
/**
* Detail about the outcome of a successful [setTbr] call.
*
* Note that all of these describe a success. In case of failure,
* [setTbr] throws an exception.
*/
enum class SetTbrOutcome {
SET_NORMAL_TBR,
SET_EMULATED_100_TBR,
LETTING_EMULATED_100_TBR_FINISH,
IGNORED_REDUNDANT_100_TBR
}
/**
* Sets the Combo's current temporary basal rate (TBR) via the remote terminal (RT) mode.
*
@ -1199,6 +1212,9 @@ class Pump(
* via the [onEvent] callback. Likewise, when a TBR finishes or is cancelled,
* [Event.TbrEnded] is emitted.
*
* When this function finishes successfully, it informs about the exact
* outcome through its return value.
*
* @param percentage TBR percentage to set.
* @param durationInMinutes TBR duration in minutes to set.
* This argument is not used if [percentage] is 100.
@ -1209,6 +1225,7 @@ class Pump(
* cancelling an ongoing TBR, which produces a W6 warning) or to fake a
* 100% TBR by setting 90% / 110% TBRs (see above).
* This argument is only used if [percentage] is 100.
* @return The specific outcome if setting the TBR succeeds.
* @throws IllegalArgumentException if the percentage is not in the 0-500 range,
* or if the percentage value is not an integer multiple of 10, or if
* the duration is <15 or not an integer multiple of 15 (see the note
@ -1244,6 +1261,7 @@ class Pump(
val currentStatus = statusFlow.value ?: throw IllegalStateException("Cannot start TBR without a known pump status")
var expectedTbrPercentage: Int
var expectedTbrDuration: Int
val result: SetTbrOutcome
// In the code below, we always create a Tbr object _before_ calling
// setCurrentTbr to make use of the checks in the Tbr constructor.
@ -1258,6 +1276,20 @@ class Pump(
reportOngoingTbrAsStopped()
expectedTbrPercentage = 100
expectedTbrDuration = 0
result = SetTbrOutcome.SET_NORMAL_TBR
} else if ((currentStatus.tbrPercentage in 90..110) && (currentStatus.remainingTbrDurationInMinutes <= 15)) {
// If the current TBR is in the 90-110% range, it is pretty much a fake 100% TBR.
// So, if that fake TBR is done within 15 minutes, we don't actually set anything.
// Instead, we just let it run. That way, the pump can actually reach 100% TBR,
// and the amount of TBR adjustments is reduced.
expectedTbrPercentage = currentStatus.tbrPercentage
expectedTbrDuration = currentStatus.remainingTbrDurationInMinutes
result = SetTbrOutcome.LETTING_EMULATED_100_TBR_FINISH
logger(LogLevel.INFO) {
"Current TBR percentage is in the 90-110% range (${currentStatus.tbrPercentage}%)," +
"and it will finish in ${currentStatus.remainingTbrDurationInMinutes} minute(s); " +
"letting it finish and faking a successful TBR set operation"
}
} else {
val newPercentage = if (currentStatus.tbrPercentage < 100) 110 else 90
val tbr = Tbr(
@ -1270,6 +1302,7 @@ class Pump(
reportStartedTbr(tbr)
expectedTbrPercentage = newPercentage
expectedTbrDuration = 15
result = SetTbrOutcome.SET_EMULATED_100_TBR
}
} else {
// Current status shows that there is no TBR ongoing. This is
@ -1278,6 +1311,7 @@ class Pump(
expectedTbrPercentage = 100
expectedTbrDuration = 0
logger(LogLevel.INFO) { "TBR was already cancelled" }
result = SetTbrOutcome.IGNORED_REDUNDANT_100_TBR
}
} else {
val tbr = Tbr(
@ -1291,6 +1325,7 @@ class Pump(
reportStartedTbr(tbr)
expectedTbrPercentage = percentage
expectedTbrDuration = durationInMinutes
result = SetTbrOutcome.SET_NORMAL_TBR
}
// We just set the TBR. Now check the main screen contents to see if
@ -1314,9 +1349,12 @@ class Pump(
throw ExtendedOrMultiwaveBolusActiveException(mainScreenContent)
is MainScreenContent.Normal -> {
if (expectedTbrPercentage != 100) {
if ((expectedTbrPercentage != 100) && (expectedTbrDuration >= 2)) {
// We expected a TBR to be active, but there isn't any;
// we aren't seen any TBR main screen contents.
// Only consider this an error if the duration is >2 minutes.
// Otherwise, this was a TBR that was about to end, so it
// might have ended while these checks here were running.
throw UnexpectedTbrStateException(
expectedTbrPercentage = expectedTbrPercentage,
expectedTbrDuration = expectedTbrDuration,
@ -1349,6 +1387,8 @@ class Pump(
}
}
}
return@executeCommand result
}
/**

View file

@ -0,0 +1,233 @@
package info.nightscout.pump.combov2
import info.nightscout.comboctl.base.BluetoothAddress
import info.nightscout.comboctl.base.CurrentTbrState
import info.nightscout.comboctl.base.InvariantPumpData
import info.nightscout.comboctl.base.Nonce
import info.nightscout.comboctl.base.PumpStateStore
import info.nightscout.comboctl.base.Tbr
import info.nightscout.comboctl.base.toBluetoothAddress
import info.nightscout.comboctl.base.toCipher
import info.nightscout.comboctl.base.toNonce
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.sharedPreferences.SPDelegateInt
import info.nightscout.shared.sharedPreferences.SPDelegateLong
import info.nightscout.shared.sharedPreferences.SPDelegateString
import kotlinx.datetime.Instant
import kotlinx.datetime.UtcOffset
import kotlin.reflect.KClassifier
/**
* Special pump state store that mainly uses the internalSP, but also is able to sync up the AAPS main SP with that internal SP.
*
* This pump state store solves a problem: What if the user already paired AAPS with a Combo, and then imports and old AAPS
* settings file, intending to just restore other settings like the basal profile, but does _not_ intend to import an old
* pump state? After all, if we write a pump state to the AAPS main SP, the state values will also be written into a settings
* file when exporting said settings. If we just relied on the main AAPS SP, the current pump state - including pairing info
* like the PC and CP keys - would be overwritten with the old ones from the imported settings, and the user would have to
* unnecessarily re-pair the Combo every time settings get imported.
*
* The solution is for this driver to _not_ primarily use the AAPS main SP. Instead, it uses its own internal SP. That SP
* is exclusively used by this driver. But, a copy of the pump state values are stored in the AAPS main SP, and kept in
* sync with the contents of the internal SP.
*
* When a new pump state is created, the invariant values (CP/PC keys etc.) are written in both the AAPS main SP and the
* internal SP, along with the initial nonce. During operation, the driver will update the nonce value of the internal SP.
* If TBR states are changed, then these changes are stored in the internal SP. Callers can use [copyVariantValuesToAAPSMainSP]
* to update the corresponding values in the AAPS main SP to keep the two SPs in sync.
*
* There are a couple of special situations:
*
* 1. There is no pump state in the internal SP, and there is no pump state in the AAPS main SP. This is the unpaired state.
* 2. There is no pump state in the internal SP, but there is one in the AAPS main SP. This typically happens when the
* user (re)installed AAPS, and immediately after installing, imported AAPS settings. Callers are then supposed to call
* [copyAllValuesFromAAPSMainSP] to import the pump state from the AAPS main SP over to the internal SP and continue to
* work with that state.
* 3. There is a pump state, and there is also one in the AAPS main SP. The latter one is then ignored. A pump state in the
* AAPS main SP solely and only exists to be able to export/import pump states. It is not used for actual pump operations.
* In particular, if - as mentioned above - a pump is already paired, and the user imports settings, this logic prevents
* the current pump state to be overwritten.
*/
class AAPSPumpStateStore(
private val aapsMainSP: SP,
private val internalSP: InternalSP
) : PumpStateStore {
private var btAddress: String
by SPDelegateString(internalSP, PreferenceKeys.BT_ADDRESS_KEY.str, "")
// The nonce is updated with commit instead of apply to make sure
// is atomically written to storage synchronously, minimizing
// the likelihood that it could be lost due to app crashes etc.
// It is very important to not lose the nonce, hence that choice.
private var nonceString: String
by SPDelegateString(internalSP, PreferenceKeys.NONCE_KEY.str, Nonce.nullNonce().toString(), commit = true)
private var cpCipherString: String
by SPDelegateString(internalSP, PreferenceKeys.CP_CIPHER_KEY.str, "")
private var pcCipherString: String
by SPDelegateString(internalSP, PreferenceKeys.PC_CIPHER_KEY.str, "")
private var keyResponseAddressInt: Int
by SPDelegateInt(internalSP, PreferenceKeys.KEY_RESPONSE_ADDRESS_KEY.str, 0)
private var pumpID: String
by SPDelegateString(internalSP, PreferenceKeys.PUMP_ID_KEY.str, "")
private var tbrTimestamp: Long
by SPDelegateLong(internalSP, PreferenceKeys.TBR_TIMESTAMP_KEY.str, 0)
private var tbrPercentage: Int
by SPDelegateInt(internalSP, PreferenceKeys.TBR_PERCENTAGE_KEY.str, 0)
private var tbrDuration: Int
by SPDelegateInt(internalSP, PreferenceKeys.TBR_DURATION_KEY.str, 0)
private var tbrType: String
by SPDelegateString(internalSP, PreferenceKeys.TBR_TYPE_KEY.str, "")
private var utcOffsetSeconds: Int
by SPDelegateInt(internalSP, PreferenceKeys.UTC_OFFSET_KEY.str, 0)
override fun createPumpState(
pumpAddress: BluetoothAddress,
invariantPumpData: InvariantPumpData,
utcOffset: UtcOffset,
tbrState: CurrentTbrState
) {
internalSP.edit(commit = true) {
putString(PreferenceKeys.BT_ADDRESS_KEY.str, pumpAddress.toString().uppercase())
putString(PreferenceKeys.CP_CIPHER_KEY.str, invariantPumpData.clientPumpCipher.toString())
putString(PreferenceKeys.PC_CIPHER_KEY.str, invariantPumpData.pumpClientCipher.toString())
putInt(PreferenceKeys.KEY_RESPONSE_ADDRESS_KEY.str, invariantPumpData.keyResponseAddress.toInt() and 0xFF)
putString(PreferenceKeys.PUMP_ID_KEY.str, invariantPumpData.pumpID)
putLong(PreferenceKeys.TBR_TIMESTAMP_KEY.str, if (tbrState is CurrentTbrState.TbrStarted) tbrState.tbr.timestamp.epochSeconds else -1)
putInt(PreferenceKeys.TBR_PERCENTAGE_KEY.str, if (tbrState is CurrentTbrState.TbrStarted) tbrState.tbr.percentage else -1)
putInt(PreferenceKeys.TBR_DURATION_KEY.str, if (tbrState is CurrentTbrState.TbrStarted) tbrState.tbr.durationInMinutes else -1)
putString(PreferenceKeys.TBR_TYPE_KEY.str, if (tbrState is CurrentTbrState.TbrStarted) tbrState.tbr.type.stringId else "")
putInt(PreferenceKeys.UTC_OFFSET_KEY.str, utcOffset.totalSeconds)
}
copyAllValuesToAAPSMainSP(commit = true)
}
override fun deletePumpState(pumpAddress: BluetoothAddress): Boolean {
val hasState = internalSP.contains(PreferenceKeys.NONCE_KEY.str)
internalSP.edit(commit = true) {
for (keys in PreferenceKeys.values())
remove(keys.str)
}
aapsMainSP.edit(commit = true) {
for (keys in PreferenceKeys.values())
remove(keys.str)
}
return hasState
}
override fun hasPumpState(pumpAddress: BluetoothAddress): Boolean =
internalSP.contains(PreferenceKeys.NONCE_KEY.str)
override fun getAvailablePumpStateAddresses(): Set<BluetoothAddress> =
if (btAddress.isBlank()) setOf() else setOf(btAddress.toBluetoothAddress())
override fun getInvariantPumpData(pumpAddress: BluetoothAddress) = InvariantPumpData(
clientPumpCipher = cpCipherString.toCipher(),
pumpClientCipher = pcCipherString.toCipher(),
keyResponseAddress = keyResponseAddressInt.toByte(),
pumpID = pumpID
)
override fun getCurrentTxNonce(pumpAddress: BluetoothAddress) = nonceString.toNonce()
override fun setCurrentTxNonce(pumpAddress: BluetoothAddress, currentTxNonce: Nonce) {
nonceString = currentTxNonce.toString()
}
override fun getCurrentUtcOffset(pumpAddress: BluetoothAddress) =
UtcOffset(seconds = utcOffsetSeconds)
override fun setCurrentUtcOffset(pumpAddress: BluetoothAddress, utcOffset: UtcOffset) {
utcOffsetSeconds = utcOffset.totalSeconds
}
override fun getCurrentTbrState(pumpAddress: BluetoothAddress) =
if (tbrTimestamp >= 0)
CurrentTbrState.TbrStarted(Tbr(
timestamp = Instant.fromEpochSeconds(tbrTimestamp),
percentage = tbrPercentage,
durationInMinutes = tbrDuration,
type = Tbr.Type.fromStringId(tbrType)!!
))
else
CurrentTbrState.NoTbrOngoing
override fun setCurrentTbrState(pumpAddress: BluetoothAddress, currentTbrState: CurrentTbrState) {
when (currentTbrState) {
is CurrentTbrState.TbrStarted -> {
tbrTimestamp = currentTbrState.tbr.timestamp.epochSeconds
tbrPercentage = currentTbrState.tbr.percentage
tbrDuration = currentTbrState.tbr.durationInMinutes
tbrType = currentTbrState.tbr.type.stringId
}
else -> {
tbrTimestamp = -1
tbrPercentage = -1
tbrDuration = -1
tbrType = ""
}
}
}
// Copies only those pump state values from the internal SP to the AAPS main SP which can vary during
// pump operations. These are the TBR values, the UTC offset, and the nonce. Users are recommended to
// call this after AAPS disconnects the pump.
fun copyVariantValuesToAAPSMainSP(commit: Boolean) =
copyValuesBetweenSPs(commit, from = internalSP, to = aapsMainSP, arrayOf(
PreferenceKeys.NONCE_KEY,
PreferenceKeys.TBR_TIMESTAMP_KEY,
PreferenceKeys.TBR_PERCENTAGE_KEY,
PreferenceKeys.TBR_DURATION_KEY,
PreferenceKeys.TBR_TYPE_KEY,
PreferenceKeys.UTC_OFFSET_KEY
))
// Copies all pump state values from the AAPS main SP to the internal SP. This is supposed to be
// called if the internal SP is empty. That way, a pump state can be imported from AAPS settings files.
fun copyAllValuesFromAAPSMainSP(commit: Boolean) =
copyValuesBetweenSPs(commit, from = aapsMainSP, to = internalSP, keys = PreferenceKeys.values())
// Copies all pump state values from the internal SP to the AAPS main SP to. The createPumpState()
// function calls this after creating the pump state to ensure both SPs are in sync. Also, this
// should be called when the driver starts in case AAPS settings are imported and there is already
// a pump state present in the internal SP. Calling this then ensures that the pump state in the
// main SP is fully synced up with the one from the internal SP, and does not contain some old
// state that is not in use anymore.
fun copyAllValuesToAAPSMainSP(commit: Boolean) =
copyValuesBetweenSPs(commit, from = internalSP, to = aapsMainSP, keys = PreferenceKeys.values())
private fun copyValuesBetweenSPs(commit: Boolean, from: SP, to: SP, keys: Array<PreferenceKeys>) {
to.edit(commit) {
for (key in keys) {
if (!from.contains(key.str))
continue
when (key.type) {
Int::class -> putInt(key.str, from.getInt(key.str, 0))
Long::class -> putLong(key.str, from.getLong(key.str, 0L))
String::class -> putString(key.str, from.getString(key.str, ""))
}
}
}
}
private enum class PreferenceKeys(val str: String, val type: KClassifier) {
BT_ADDRESS_KEY("combov2-bt-address-key", String::class),
NONCE_KEY("combov2-nonce-key", String::class),
CP_CIPHER_KEY("combov2-cp-cipher-key", String::class),
PC_CIPHER_KEY("combov2-pc-cipher-key", String::class),
KEY_RESPONSE_ADDRESS_KEY("combov2-key-response-address-key", Int::class),
PUMP_ID_KEY("combov2-pump-id-key", String::class),
TBR_TIMESTAMP_KEY("combov2-tbr-timestamp", Long::class),
TBR_PERCENTAGE_KEY("combov2-tbr-percentage", Int::class),
TBR_DURATION_KEY("combov2-tbr-duration", Int::class),
TBR_TYPE_KEY("combov2-tbr-type", String::class),
UTC_OFFSET_KEY("combov2-utc-offset", Int::class);
override fun toString(): String = str
}
}

View file

@ -133,7 +133,17 @@ class ComboV2Plugin @Inject constructor (
private val _pumpDescription = PumpDescription()
private val pumpStateStore = SPPumpStateStore(sp)
// The internal SP is the one that will be mainly used by the driver.
// The AAPS main SP is updated when the pump state store is created
// and when the driver disconnects (to update the nonce value).
private val internalSP = InternalSP(
context.getSharedPreferences(
context.packageName + ".COMBO_PUMP_STATE_STORE",
Context.MODE_PRIVATE
),
context
)
private val pumpStateStore = AAPSPumpStateStore(aapsMainSP = sp, internalSP = internalSP)
// These are initialized in onStart() and torn down in onStop().
private var bluetoothInterface: AndroidBluetoothInterface? = null
@ -236,6 +246,34 @@ class ComboV2Plugin @Inject constructor (
override fun onStart() {
super.onStart()
// Check if there is a pump state in the internal SP. If not, try to
// copy a pump state from the AAPS main SP. It is possible for example
// that AAPS was reinstalled, and the previous settings were imported.
// In that case, the internal SP is empty, but there is a pump state
// that comes from the settings. We want to restore that pump state
// then. If however, there _is_ a pump state in the internal SP, then
// we just ignore any state in the main SP. For example, if the user
// imports an older AAPS settings file with an old pump state, and a
// Combo is already paired with AAPS, then it makes no sense to overwrite
// the current pump state with the old one from the imported settings.
if (pumpStateStore.getAvailablePumpStateAddresses().isEmpty()) {
aapsLogger.info(LTag.PUMP, "There is no pump state in the internal SP; trying to copy a pump state from the main AAPS SP")
pumpStateStore.copyAllValuesFromAAPSMainSP(commit = true)
val btAddress = pumpStateStore.getAvailablePumpStateAddresses().firstOrNull()
if (btAddress == null)
aapsLogger.info(LTag.PUMP, "No pump state found in the main AAPS SP; continuing without a pump state (implying that no pump is paired)")
else
aapsLogger.info(LTag.PUMP, "Pump state found in the main AAPS SP (bluetooth address: $btAddress); continuing with that state")
} else {
// Copy over the internal SP pump state to the main AAPS SP. If the user
// just imported AAPS settings, and said settings contained an old pump
// state, then that old pump state is ignored if there is already a
// current pump state in the internal SP - but we still need to make sure
// the old pump state in the main AAPS SP is replaced by the current one.
aapsLogger.debug(LTag.PUMP, "Copying internal SP pump state to main AAPS SP")
pumpStateStore.copyAllValuesToAAPSMainSP(commit = false)
}
aapsLogger.debug(LTag.PUMP, "Creating bluetooth interface")
bluetoothInterface = AndroidBluetoothInterface(context)
@ -624,6 +662,11 @@ class ComboV2Plugin @Inject constructor (
override fun disconnect(reason: String) {
aapsLogger.debug(LTag.PUMP, "Disconnecting from Combo; reason: $reason")
disconnectInternal(forceDisconnect = false)
// Sync up the TBR and nonce states in the main AAPS SP. We don't do this all the
// time since this is unnecessary waste of resources. It is sufficient to update
// those once AAPS is done with the connection.
pumpStateStore.copyVariantValuesToAAPSMainSP(commit = false)
}
// This is called when (a) the AAPS watchdog is about to toggle
@ -1048,17 +1091,34 @@ class ComboV2Plugin @Inject constructor (
return pumpEnactResult
}
private fun setTbrInternal(percentage: Int, durationInMinutes: Int, tbrType: ComboCtlTbr.Type, force100Percent: Boolean, pumpEnactResult: PumpEnactResult) {
private fun setTbrInternal(
percentage: Int,
durationInMinutes: Int,
tbrType: ComboCtlTbr.Type,
force100Percent: Boolean,
pumpEnactResult: PumpEnactResult
) {
runBlocking {
try {
executeCommand {
pump!!.setTbr(percentage, durationInMinutes, tbrType, force100Percent)
}
val setTbrOutcome = pump!!.setTbr(percentage, durationInMinutes, tbrType, force100Percent)
pumpEnactResult.apply {
success = true
enacted = true
comment = rh.gs(R.string.combov2_setting_tbr_succeeded)
val tbrComment = when (setTbrOutcome) {
ComboCtlPump.SetTbrOutcome.SET_NORMAL_TBR ->
rh.gs(R.string.combov2_setting_tbr_succeeded)
ComboCtlPump.SetTbrOutcome.SET_EMULATED_100_TBR ->
rh.gs(R.string.combov2_set_emulated_100_tbr)
ComboCtlPump.SetTbrOutcome.LETTING_EMULATED_100_TBR_FINISH ->
rh.gs(R.string.combov2_letting_emulated_100_tbr_finish)
ComboCtlPump.SetTbrOutcome.IGNORED_REDUNDANT_100_TBR ->
rh.gs(R.string.combov2_ignoring_redundant_100_tbr)
}
pumpEnactResult.apply {
success = true
enacted = true
comment = tbrComment
}
}
} catch (e: QuantityNotChangingException) {
aapsLogger.error(LTag.PUMP, "TBR percentage adjustment hit a limit: $e")

View file

@ -0,0 +1,199 @@
package info.nightscout.pump.combov2
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import androidx.annotation.StringRes
import info.nightscout.shared.SafeParse
import info.nightscout.shared.sharedPreferences.SP
// This is a copy of the AAPS SPImplementation. We keep this to be able
// to set up a custom internal SP store for the Combo pump state.
class InternalSP(
private val sharedPreferences: SharedPreferences,
private val context: Context
) : SP {
@SuppressLint("ApplySharedPref")
override fun edit(commit: Boolean, block: SP.Editor.() -> Unit) {
val spEdit = sharedPreferences.edit()
val edit = object : SP.Editor {
override fun clear() {
spEdit.clear()
}
override fun remove(@StringRes resourceID: Int) {
spEdit.remove(context.getString(resourceID))
}
override fun remove(key: String) {
spEdit.remove(key)
}
override fun putBoolean(key: String, value: Boolean) {
spEdit.putBoolean(key, value)
}
override fun putBoolean(@StringRes resourceID: Int, value: Boolean) {
spEdit.putBoolean(context.getString(resourceID), value)
}
override fun putDouble(key: String, value: Double) {
spEdit.putString(key, value.toString())
}
override fun putDouble(@StringRes resourceID: Int, value: Double) {
spEdit.putString(context.getString(resourceID), value.toString())
}
override fun putLong(key: String, value: Long) {
spEdit.putLong(key, value)
}
override fun putLong(@StringRes resourceID: Int, value: Long) {
spEdit.putLong(context.getString(resourceID), value)
}
override fun putInt(key: String, value: Int) {
spEdit.putInt(key, value)
}
override fun putInt(@StringRes resourceID: Int, value: Int) {
spEdit.putInt(context.getString(resourceID), value)
}
override fun putString(key: String, value: String) {
spEdit.putString(key, value)
}
override fun putString(@StringRes resourceID: Int, value: String) {
spEdit.putString(context.getString(resourceID), value)
}
}
block(edit)
if (commit)
spEdit.commit()
else
spEdit.apply()
}
override fun getAll(): Map<String, *> = sharedPreferences.all
override fun clear() = sharedPreferences.edit().clear().apply()
override fun contains(key: String): Boolean = sharedPreferences.contains(key)
override fun contains(resourceId: Int): Boolean = sharedPreferences.contains(context.getString(resourceId))
override fun remove(resourceID: Int) =
sharedPreferences.edit().remove(context.getString(resourceID)).apply()
override fun remove(key: String) =
sharedPreferences.edit().remove(key).apply()
override fun getString(resourceID: Int, defaultValue: String): String =
sharedPreferences.getString(context.getString(resourceID), defaultValue) ?: defaultValue
override fun getStringOrNull(resourceID: Int, defaultValue: String?): String? =
sharedPreferences.getString(context.getString(resourceID), defaultValue) ?: defaultValue
override fun getStringOrNull(key: String, defaultValue: String?): String? =
sharedPreferences.getString(key, defaultValue)
override fun getString(key: String, defaultValue: String): String =
sharedPreferences.getString(key, defaultValue) ?: defaultValue
override fun getBoolean(resourceID: Int, defaultValue: Boolean): Boolean {
return try {
sharedPreferences.getBoolean(context.getString(resourceID), defaultValue)
} catch (e: Exception) {
defaultValue
}
}
override fun getBoolean(key: String, defaultValue: Boolean): Boolean {
return try {
sharedPreferences.getBoolean(key, defaultValue)
} catch (e: Exception) {
defaultValue
}
}
override fun getDouble(resourceID: Int, defaultValue: Double): Double =
SafeParse.stringToDouble(sharedPreferences.getString(context.getString(resourceID), defaultValue.toString()))
override fun getDouble(key: String, defaultValue: Double): Double =
SafeParse.stringToDouble(sharedPreferences.getString(key, defaultValue.toString()))
override fun getInt(resourceID: Int, defaultValue: Int): Int {
return try {
sharedPreferences.getInt(context.getString(resourceID), defaultValue)
} catch (e: Exception) {
SafeParse.stringToInt(sharedPreferences.getString(context.getString(resourceID), defaultValue.toString()))
}
}
override fun getInt(key: String, defaultValue: Int): Int {
return try {
sharedPreferences.getInt(key, defaultValue)
} catch (e: Exception) {
SafeParse.stringToInt(sharedPreferences.getString(key, defaultValue.toString()))
}
}
override fun getLong(resourceID: Int, defaultValue: Long): Long {
return try {
sharedPreferences.getLong(context.getString(resourceID), defaultValue)
} catch (e: Exception) {
try {
SafeParse.stringToLong(sharedPreferences.getString(context.getString(resourceID), defaultValue.toString()))
} catch (e: Exception) {
defaultValue
}
}
}
override fun getLong(key: String, defaultValue: Long): Long {
return try {
sharedPreferences.getLong(key, defaultValue)
} catch (e: Exception) {
try {
SafeParse.stringToLong(sharedPreferences.getString(key, defaultValue.toString()))
} catch (e: Exception) {
defaultValue
}
}
}
override fun incLong(resourceID: Int) {
val value = getLong(resourceID, 0) + 1L
sharedPreferences.edit().putLong(context.getString(resourceID), value).apply()
}
override fun putBoolean(key: String, value: Boolean) = sharedPreferences.edit().putBoolean(key, value).apply()
override fun putBoolean(resourceID: Int, value: Boolean) =
sharedPreferences.edit().putBoolean(context.getString(resourceID), value).apply()
override fun putDouble(key: String, value: Double) =
sharedPreferences.edit().putString(key, value.toString()).apply()
override fun putDouble(resourceID: Int, value: Double) {
sharedPreferences.edit().putString(context.getString(resourceID), value.toString()).apply()
}
override fun putLong(key: String, value: Long) =
sharedPreferences.edit().putLong(key, value).apply()
override fun putLong(resourceID: Int, value: Long) =
sharedPreferences.edit().putLong(context.getString(resourceID), value).apply()
override fun putInt(key: String, value: Int) =
sharedPreferences.edit().putInt(key, value).apply()
override fun putInt(resourceID: Int, value: Int) =
sharedPreferences.edit().putInt(context.getString(resourceID), value).apply()
override fun incInt(resourceID: Int) {
val value = getInt(resourceID, 0) + 1
sharedPreferences.edit().putInt(context.getString(resourceID), value).apply()
}
override fun putString(resourceID: Int, value: String) =
sharedPreferences.edit().putString(context.getString(resourceID), value).apply()
override fun putString(key: String, value: String) =
sharedPreferences.edit().putString(key, value).apply()
}

View file

@ -1,165 +0,0 @@
package info.nightscout.pump.combov2
import info.nightscout.comboctl.base.BluetoothAddress
import info.nightscout.comboctl.base.CurrentTbrState
import info.nightscout.comboctl.base.InvariantPumpData
import info.nightscout.comboctl.base.Nonce
import info.nightscout.comboctl.base.PumpStateStore
import info.nightscout.comboctl.base.Tbr
import info.nightscout.comboctl.base.toBluetoothAddress
import info.nightscout.comboctl.base.toCipher
import info.nightscout.comboctl.base.toNonce
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.sharedPreferences.SPDelegateInt
import info.nightscout.shared.sharedPreferences.SPDelegateLong
import info.nightscout.shared.sharedPreferences.SPDelegateString
import kotlinx.datetime.Instant
import kotlinx.datetime.UtcOffset
/**
* AndroidAPS [SP] based pump state store.
*
* This store is set up to contain a single paired pump. AndroidAPS is not
* designed to handle multiple pumps, so this simplification makes sense.
* This affects all accessors, which
*/
class SPPumpStateStore(private val sp: SP) : PumpStateStore {
private var btAddress: String
by SPDelegateString(sp, BT_ADDRESS_KEY, "")
// The nonce is updated with commit instead of apply to make sure
// is atomically written to storage synchronously, minimizing
// the likelihood that it could be lost due to app crashes etc.
// It is very important to not lose the nonce, hence that choice.
private var nonceString: String
by SPDelegateString(sp, NONCE_KEY, Nonce.nullNonce().toString(), commit = true)
private var cpCipherString: String
by SPDelegateString(sp, CP_CIPHER_KEY, "")
private var pcCipherString: String
by SPDelegateString(sp, PC_CIPHER_KEY, "")
private var keyResponseAddressInt: Int
by SPDelegateInt(sp, KEY_RESPONSE_ADDRESS_KEY, 0)
private var pumpID: String
by SPDelegateString(sp, PUMP_ID_KEY, "")
private var tbrTimestamp: Long
by SPDelegateLong(sp, TBR_TIMESTAMP_KEY, 0)
private var tbrPercentage: Int
by SPDelegateInt(sp, TBR_PERCENTAGE_KEY, 0)
private var tbrDuration: Int
by SPDelegateInt(sp, TBR_DURATION_KEY, 0)
private var tbrType: String
by SPDelegateString(sp, TBR_TYPE_KEY, "")
private var utcOffsetSeconds: Int
by SPDelegateInt(sp, UTC_OFFSET_KEY, 0)
override fun createPumpState(
pumpAddress: BluetoothAddress,
invariantPumpData: InvariantPumpData,
utcOffset: UtcOffset,
tbrState: CurrentTbrState
) {
// Write these values via edit() instead of using the delegates
// above to be able to write all of them with a single commit.
sp.edit(commit = true) {
putString(BT_ADDRESS_KEY, pumpAddress.toString().uppercase())
putString(CP_CIPHER_KEY, invariantPumpData.clientPumpCipher.toString())
putString(PC_CIPHER_KEY, invariantPumpData.pumpClientCipher.toString())
putInt(KEY_RESPONSE_ADDRESS_KEY, invariantPumpData.keyResponseAddress.toInt() and 0xFF)
putString(PUMP_ID_KEY, invariantPumpData.pumpID)
putLong(TBR_TIMESTAMP_KEY, if (tbrState is CurrentTbrState.TbrStarted) tbrState.tbr.timestamp.epochSeconds else -1)
putInt(TBR_PERCENTAGE_KEY, if (tbrState is CurrentTbrState.TbrStarted) tbrState.tbr.percentage else -1)
putInt(TBR_DURATION_KEY, if (tbrState is CurrentTbrState.TbrStarted) tbrState.tbr.durationInMinutes else -1)
putString(TBR_TYPE_KEY, if (tbrState is CurrentTbrState.TbrStarted) tbrState.tbr.type.stringId else "")
putInt(UTC_OFFSET_KEY, utcOffset.totalSeconds)
}
}
override fun deletePumpState(pumpAddress: BluetoothAddress): Boolean {
val hasState = sp.contains(NONCE_KEY)
sp.edit(commit = true) {
remove(BT_ADDRESS_KEY)
remove(NONCE_KEY)
remove(CP_CIPHER_KEY)
remove(PC_CIPHER_KEY)
remove(KEY_RESPONSE_ADDRESS_KEY)
remove(TBR_TIMESTAMP_KEY)
remove(TBR_PERCENTAGE_KEY)
remove(TBR_DURATION_KEY)
remove(TBR_TYPE_KEY)
remove(UTC_OFFSET_KEY)
}
return hasState
}
override fun hasPumpState(pumpAddress: BluetoothAddress) =
sp.contains(NONCE_KEY)
override fun getAvailablePumpStateAddresses() =
if (btAddress.isBlank()) setOf() else setOf(btAddress.toBluetoothAddress())
override fun getInvariantPumpData(pumpAddress: BluetoothAddress) = InvariantPumpData(
clientPumpCipher = cpCipherString.toCipher(),
pumpClientCipher = pcCipherString.toCipher(),
keyResponseAddress = keyResponseAddressInt.toByte(),
pumpID = pumpID
)
override fun getCurrentTxNonce(pumpAddress: BluetoothAddress) = nonceString.toNonce()
override fun setCurrentTxNonce(pumpAddress: BluetoothAddress, currentTxNonce: Nonce) {
nonceString = currentTxNonce.toString()
}
override fun getCurrentUtcOffset(pumpAddress: BluetoothAddress) =
UtcOffset(seconds = utcOffsetSeconds)
override fun setCurrentUtcOffset(pumpAddress: BluetoothAddress, utcOffset: UtcOffset) {
utcOffsetSeconds = utcOffset.totalSeconds
}
override fun getCurrentTbrState(pumpAddress: BluetoothAddress) =
if (tbrTimestamp >= 0)
CurrentTbrState.TbrStarted(Tbr(
timestamp = Instant.fromEpochSeconds(tbrTimestamp),
percentage = tbrPercentage,
durationInMinutes = tbrDuration,
type = Tbr.Type.fromStringId(tbrType)!!
))
else
CurrentTbrState.NoTbrOngoing
override fun setCurrentTbrState(pumpAddress: BluetoothAddress, currentTbrState: CurrentTbrState) {
when (currentTbrState) {
is CurrentTbrState.TbrStarted -> {
tbrTimestamp = currentTbrState.tbr.timestamp.epochSeconds
tbrPercentage = currentTbrState.tbr.percentage
tbrDuration = currentTbrState.tbr.durationInMinutes
tbrType = currentTbrState.tbr.type.stringId
}
else -> {
tbrTimestamp = -1
tbrPercentage = -1
tbrDuration = -1
tbrType = ""
}
}
}
companion object {
const val BT_ADDRESS_KEY = "combov2-bt-address-key"
const val NONCE_KEY = "combov2-nonce-key"
const val CP_CIPHER_KEY = "combov2-cp-cipher-key"
const val PC_CIPHER_KEY = "combov2-pc-cipher-key"
const val KEY_RESPONSE_ADDRESS_KEY = "combov2-key-response-address-key"
const val PUMP_ID_KEY = "combov2-pump-id-key"
const val TBR_TIMESTAMP_KEY = "combov2-tbr-timestamp"
const val TBR_PERCENTAGE_KEY = "combov2-tbr-percentage"
const val TBR_DURATION_KEY = "combov2-tbr-duration"
const val TBR_TYPE_KEY = "combov2-tbr-type"
const val UTC_OFFSET_KEY = "combov2-utc-offset"
}
}

View file

@ -71,6 +71,7 @@ knappene samtidig for å avbryte parringen)\n
<string name="combov2_fetching_tdd_history_cmddesc">Henter TDD historikk</string>
<string name="combov2_updating_pump_datetime_cmddesc">Oppdaterer dato/klokkeslett i pumpen</string>
<string name="combov2_updating_pump_status_cmddesc">Oppdaterer pumpestatus</string>
<string name="combov2_pairing_pin_failure">PIN-koden fungerte ikke. Sjekk om det var en skrivefeil. Hvis dette fortsetter å skje, avbryt og prøv å pare enhet på nytt.</string>
<string name="combov2_discovery_duration">Søkevarighet (i sekunder)</string>
<string name="combov2_verbose_logging">Aktiver detaljert Combo logging</string>
<string name="combov2_getting_basal_profile">Henter basalprofil; %1$d registrering(er) lest</string>
@ -109,4 +110,12 @@ knappene samtidig for å avbryte parringen)\n
<string name="combov2_short_status_battery_state_low">lavt</string>
<string name="combov2_short_status_battery_state_full">full</string>
<string name="combov2_short_status_battery_state">Batt: %s</string>
<string name="combov2_automatic_reservoir_entry">Autodetekter og automatisk registrer bytte av insulireservoar</string>
<string name="combov2_automatic_battery_entry">Autodetekter og automatisk registrer batteribytte</string>
<string name="combov2_note_reservoir_change">Bytte av insulinreservoar ble automatisk registrert av Combo driveren</string>
<string name="combov2_note_battery_change">Bytte av batteri ble automatisk registrert av Combo driveren</string>
<string name="combov2_timezone_changed">Tidssone endret</string>
<string name="combov2_datetime_changed">Dato og/eller klokkeslett endret</string>
<string name="combov2_dst_started">Sommertid (DST) er startet</string>
<string name="combov2_dst_ended">Sommertid (DST) er avsluttet</string>
</resources>

View file

@ -28,14 +28,73 @@
<string name="bluetooth_address">Adresa Bluetooth</string>
<string name="combov2_start_pairing">Spustiť párovanie</string>
<string name="combov2_pairing_in_progress">Prebieha Combo párovanie</string>
<string name="combov2_pairing_start_steps">Kroky k vykonaniu párovania s Combo:\n\n
1. Na vašej pumpe prejdite do nastavení Bluetooth\n
2. Zkontrolujte, či je zariadenie už zobrazené ako spárované. pokiaľ áno, prejdite na obrazovku pumpy \"Odstrániť zariadenie\" a odstrániť/zrušiť párovanie tohoto zariadenia\n
3. Prejdite na obrazovku pumpy \"Pridať zariadenie\" a spárujte na pumpe\n
4. Kliknite na tlačítko \"Spustiť párovanie\" nižšie pre zahájenie párovania v AndroidAPS\n</string>
<string name="combov2_steps_if_no_connection_during_pairing">Po nejakej dobe sa na obrazovke pumpy zobrazí meno telefónu; stlačte KONTROLA pre potvrdenie.\n\n
Po úspešnom dokončení párovania, potvrďte dokončenie párovania na vašej pumpe a vráťte sa na hlavnú obrazovku pumpy dvojitým stlačením tlačítka KONIEC.\n\n
Pokiaľ po viac ako ~5 minútach nie je nadviazané žiadne spojenie:\n\n
1. Stlačte tlačítko Späť alebo tlačítko \"Zrušiť párovanie\"\n
2. Zrušte párovanie na Combo (súčasne stlačte tlačítka „UP i MENU“ pre zrušenie párovania)\n
3. Skúste znova spárovať</string>
<string name="combov2_enter_pin">Zadajte PIN</string>
<string name="combov2_cancel_pairing">Zrušiť párovanie</string>
<string name="combov2_pin_hint">10-miestny PIN</string>
<string name="combov2_pairing_finished_successfully">Úspešne spárované s Combo</string>
<string name="combov2_pairing_cancelled">Párovanie s Combo zrušené užívateľom</string>
<string name="combov2_pairing_combo_scan_timeout_reached">Časový limit pre vyhľadávanie Combo dosiahnutý</string>
<string name="combov2_pairing_failed_due_to_error">Párovanie se nepodarilo z dôvodu chyby: %1$s</string>
<string name="combov2_pairing_aborted_unknown_reasons">Párovanie prerušené z neznámych dôvodov</string>
<string name="combov2_scanning_for_pump">Vyhľadávanie pumpy</string>
<string name="combov2_establishing_bt_connection">Nadväzovanie Bluetooth pripojenia (pokus č. %1$d)</string>
<string name="combov2_pairing_performing_handshake">Nadväzovanie pripojenia s pumpou</string>
<string name="combov2_pairing_pump_requests_pin">Pumpa vyžaduje desaťmiestny kód PIN</string>
<string name="combov2_pairing_finishing">Dokončovanie párovania</string>
<string name="combov2_no_connection_for_n_mins">Žiadne spojenie v priebehu %1$d min</string>
<string name="combov2_less_than_one_minute_ago">Pred menej ako minútou</string>
<string name="combov2_setting_current_pump_time">Nastavenie aktuálneho času pumpy</string>
<string name="combov2_setting_current_pump_date">Nastavenie aktuálneho dátumu pumpy</string>
<string name="combov2_not_initialized">Nie je inicializovaný</string>
<string name="combov2_checking_pump">Kontrola pumpy</string>
<string name="combov2_ready">Pripravený</string>
<string name="combov2_suspended">Pozastavené</string>
<string name="combov2_pump_is_suspended">Pumpa je pozastavená</string>
<string name="combov2_executing_command">Vykonávanie príkazu</string>
<string name="combov2_getting_basal_profile_cmddesc">Načítavam bazálny profil</string>
<string name="combov2_setting_basal_profile_cmddesc">Nastavenie bazálneho profilu</string>
<string name="combov2_setting_tbr_cmddesc">Nastavenie %1$d%% dočasného bazálu na %2$d minút</string>
<string name="combov2_cancelling_tbr">Zrušenie prebiehajúceho dočasného bazálu</string>
<string name="combov2_delivering_bolus_cmddesc">Podávanie %1$.1f JI</string>
<string name="combov2_fetching_tdd_history_cmddesc">Načítavam CDD históriu</string>
<string name="combov2_updating_pump_datetime_cmddesc">Aktualizácia dátumu a času v pumpe</string>
<string name="combov2_updating_pump_status_cmddesc">Načítavam stav pumpy</string>
<string name="combov2_pairing_pin_failure">PIN nefungoval. Skontrolujte, či ste neurobili preklep. Pokiaľ sa to stále deje, zrušte a opakujte párovanie.</string>
<string name="combov2_discovery_duration">Dĺžka hľadania (v sekundách)</string>
<string name="combov2_verbose_logging">Povoliť podrobné logovanie Combo</string>
<string name="combov2_getting_basal_profile">Získávanie bazálneho profilu; čítanie %1$d. faktora</string>
<string name="combov2_setting_basal_profile">Nastavenie bazálneho profilu; zapisovanie %1$d. faktora</string>
<string name="combov2_delivering_bolus">Podávanie bolusu (podané %1$.1f z %2$.1f JI)</string>
<string name="combov2_cannot_deliver_pump_suspended">Nie je možné podať bolus - pumpa je pozastavená</string>
<string name="combov2_insufficient_insulin_in_reservoir">Nedostatok inzulínu v zásobníku</string>
<string name="combov2_bolus_cancelled">Bolus zrušený</string>
<string name="combov2_bolus_delivery_failed">Podávanie inzulínu zlyhalo. Zdá sa, že nebol podaný žiadny bolus. Aby ste si boli istí, skontrolujte pumpu a prípadne pošlite bolus znovu. Aby sa zabránilo nechcenému dvojitému bolusu, podanie bolusu nie je automaticky opakované.</string>
<string name="combov2_bolus_not_delivered">Bolus nebol podaný</string>
<string name="combov2_cannot_access_pump_data">Nie je možné získať prístup k dátam pumpy; pumpa musí byť znova spárovaná</string>
<string name="combov2_unaccounted_bolus_detected_cancelling_bolus">Zistené nečakané bolusy. Z bezpečnostných dôvodov bolus zrušený.</string>
<string name="combov2_incorrect_active_basal_profile">Nesprávny aktívny bazálny profil; profil 1 musí byť aktívny, nie profil %1$d</string>
<string name="combov2_unrecognized_alert">Nerozpoznané Combo upozornenie</string>
<string name="combov2_combo_alert">Combo upozornenie</string>
<string name="combov2_current_tbr">%1$d%% (%2$d min zostáva)</string>
<string name="combov2_current_tbr_less_than_1min">%1$d%% (zostáva menej ako 1 min)</string>
<string name="combov2_load_tdds_cancelled">Načítanie CDD zrušené</string>
<string name="combov2_retrieving_tdds_failed">Načítanie CDD zlyhalo</string>
<string name="combov2_battery_low_warning">Batéria v pumpe je takmer vybitá</string>
<string name="combov2_reservoir_low_warning">V zásobníku je málo inzulínu</string>
<string name="combov2_setting_tbr_succeeded">Nastavenie dočasného bazálu bolo úspešné</string>
<string name="combov2_setting_tbr_failed">Nastavenie dočasného bazálu zlyhalo</string>
<string name="combov2_hit_unexpected_tbr_limit">Pri úprave dočasného bazálu došlo k neočekávanému limitu: vyžadované %1$d%%, dosiahnutý limit %1$d%%</string>
<string name="combov2_short_status_last_connection">Posledné spoj: pred %1$d min</string>
<string name="combov2_short_status_temp_basal">Doč. bazál: %s</string>
<string name="combov2_short_status_reservoir">Zásobník: %dJI</string>

View file

@ -107,6 +107,9 @@ buttons at the same time to cancel pairing)\n
<string name="combov2_reservoir_low_warning">Pump reservoir level is low</string>
<string name="combov2_setting_tbr_succeeded">Setting TBR succeeded</string>
<string name="combov2_setting_tbr_failed">Setting TBR failed</string>
<string name="combov2_set_emulated_100_tbr">Set emulated 100% TBR</string>
<string name="combov2_letting_emulated_100_tbr_finish">Letting ongoing emulated 100% TBR finish</string>
<string name="combov2_ignoring_redundant_100_tbr">Ignoring redundant 100% TBR request</string>
<string name="combov2_hit_unexpected_tbr_limit">Unexpected limit encountered while adjusting TBR: target percentage was %1$d%%, hit a limit at %1$d%%</string>
<string name="combov2_cannot_set_absolute_tbr_if_basal_zero">Cannot set absolute TBR if base basal rate is zero</string>
<string name="combov2_pair_with_pump_summary">Pair AndroidAPS and Android with a currently unpaired Accu-Chek Combo pump</string>

View file

@ -20,7 +20,7 @@
android:key="@string/key_combov2_unpair_pump"
android:shouldDisableView="true"/>
<SeekBarPreference
<info.nightscout.core.ui.elements.IntSeekBarPreference
android:key="@string/key_combov2_discovery_duration"
android:title="@string/combov2_discovery_duration"
android:min="30"

View file

@ -157,7 +157,7 @@ class DanaRKoreanPlugin @Inject constructor(
result
} else {
val result = PumpEnactResult(injector)
result.success(false).bolusDelivered(0.0).carbsDelivered(0.0).comment(info.nightscout.core.ui.R.string.invalid_input)
result.success(false).bolusDelivered(0.0).comment(info.nightscout.core.ui.R.string.invalid_input)
aapsLogger.error("deliverTreatment: Invalid input")
result
}

View file

@ -197,8 +197,7 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin {
connectionOK = sExecutionService.bolus(detailedBolusInfo.insulin, (int) carbs, carbTimeStamp, t);
PumpEnactResult result = new PumpEnactResult(getInjector());
result.success(connectionOK && Math.abs(detailedBolusInfo.insulin - t.getInsulin()) < pumpDescription.getBolusStep())
.bolusDelivered(t.getInsulin())
.carbsDelivered(detailedBolusInfo.carbs);
.bolusDelivered(t.getInsulin());
if (!result.getSuccess())
result.comment(rh.gs(info.nightscout.pump.dana.R.string.boluserrorcode, detailedBolusInfo.insulin, t.getInsulin(),
danaPump.getBolusStartErrorCode()));
@ -209,7 +208,7 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin {
return result;
} else {
PumpEnactResult result = new PumpEnactResult(getInjector());
result.success(false).bolusDelivered(0d).carbsDelivered(0d).comment(info.nightscout.core.ui.R.string.invalid_input);
result.success(false).bolusDelivered(0d).comment(info.nightscout.core.ui.R.string.invalid_input);
aapsLogger.error("deliverTreatment: Invalid input");
return result;
}

View file

@ -120,6 +120,7 @@ public abstract class AbstractDanaRPlugin extends PumpPluginBase implements Pump
}
})
);
danaPump.setSerialNumber(sp.getString(info.nightscout.pump.dana.R.string.key_danar_bt_name, "")); // fill at start to allow password reset
}
@Override protected void onStop() {

View file

@ -172,8 +172,7 @@ public class DanaRPlugin extends AbstractDanaRPlugin {
connectionOK = sExecutionService.bolus(detailedBolusInfo.insulin, (int) detailedBolusInfo.carbs, detailedBolusInfo.getCarbsTimestamp() != null ? detailedBolusInfo.getCarbsTimestamp() : detailedBolusInfo.timestamp, t);
PumpEnactResult result = new PumpEnactResult(getInjector());
result.success(connectionOK && Math.abs(detailedBolusInfo.insulin - t.getInsulin()) < pumpDescription.getBolusStep())
.bolusDelivered(t.getInsulin())
.carbsDelivered(detailedBolusInfo.carbs);
.bolusDelivered(t.getInsulin());
if (!result.getSuccess())
result.comment(rh.gs(info.nightscout.pump.dana.R.string.boluserrorcode, detailedBolusInfo.insulin, t.getInsulin(), danaPump.getBolusStartErrorCode()));
else
@ -199,7 +198,7 @@ public class DanaRPlugin extends AbstractDanaRPlugin {
return result;
} else {
PumpEnactResult result = new PumpEnactResult(getInjector());
result.success(false).bolusDelivered(0d).carbsDelivered(0d).comment(info.nightscout.core.ui.R.string.invalid_input);
result.success(false).bolusDelivered(0d).comment(info.nightscout.core.ui.R.string.invalid_input);
aapsLogger.error("deliverTreatment: Invalid input");
return result;
}

View file

@ -42,6 +42,7 @@ class DanaRPluginTest : TestBaseWithProfile() {
@BeforeEach
fun prepareMocks() {
`when`(sp.getString(info.nightscout.pump.dana.R.string.key_danars_address, "")).thenReturn("")
`when`(sp.getString(info.nightscout.pump.dana.R.string.key_danar_bt_name, "")).thenReturn("")
`when`(rh.gs(info.nightscout.core.ui.R.string.pumplimit)).thenReturn("pump limit")
`when`(rh.gs(info.nightscout.core.ui.R.string.itmustbepositivevalue)).thenReturn("it must be positive value")
`when`(rh.gs(info.nightscout.core.ui.R.string.limitingbasalratio)).thenReturn("Limiting max basal rate to %1\$.2f U/h because of %2\$s")

View file

@ -42,6 +42,7 @@ class DanaRKoreanPluginTest : TestBaseWithProfile() {
@BeforeEach
fun prepareMocks() {
`when`(sp.getString(info.nightscout.pump.dana.R.string.key_danars_address, "")).thenReturn("")
`when`(sp.getString(info.nightscout.pump.dana.R.string.key_danar_bt_name, "")).thenReturn("")
`when`(rh.gs(info.nightscout.core.ui.R.string.pumplimit)).thenReturn("pump limit")
`when`(rh.gs(info.nightscout.core.ui.R.string.itmustbepositivevalue)).thenReturn("it must be positive value")
`when`(rh.gs(info.nightscout.core.ui.R.string.limitingbasalratio)).thenReturn("Limiting max basal rate to %1\$.2f U/h because of %2\$s")

View file

@ -44,15 +44,17 @@ class DanaRv2PluginTest : TestBaseWithProfile() {
@BeforeEach
fun prepareMocks() {
`when`(sp.getString(info.nightscout.pump.dana.R.string.key_danars_address, ""))
.thenReturn("")
`when`(sp.getString(info.nightscout.pump.dana.R.string.key_danar_bt_name, "")).thenReturn("")
`when`(sp.getString(info.nightscout.pump.dana.R.string.key_danars_address, "")).thenReturn("")
`when`(rh.gs(info.nightscout.core.ui.R.string.pumplimit)).thenReturn("pump limit")
`when`(rh.gs(info.nightscout.core.ui.R.string.itmustbepositivevalue)).thenReturn("it must be positive value")
`when`(rh.gs(info.nightscout.core.ui.R.string.limitingbasalratio)).thenReturn("Limiting max basal rate to %1\$.2f U/h because of %2\$s")
`when`(rh.gs(info.nightscout.core.ui.R.string.limitingpercentrate)).thenReturn("Limiting max percent rate to %1\$d%% because of %2\$s")
danaPump = DanaPump(aapsLogger, sp, dateUtil, profileInstantiator)
danaRv2Plugin = DanaRv2Plugin(injector, aapsLogger, aapsSchedulers, rxBus, context, rh, constraintChecker, activePluginProvider, sp, commandQueue, danaPump, detailedBolusInfoStorage,
temporaryBasalStorage, dateUtil, fabricPrivacy, pumpSync, uiInteraction, danaHistoryDatabase)
danaRv2Plugin = DanaRv2Plugin(
injector, aapsLogger, aapsSchedulers, rxBus, context, rh, constraintChecker, activePluginProvider, sp, commandQueue, danaPump, detailedBolusInfoStorage,
temporaryBasalStorage, dateUtil, fabricPrivacy, pumpSync, uiInteraction, danaHistoryDatabase
)
}
@Test

View file

@ -154,6 +154,7 @@ class DanaRSPlugin @Inject constructor(
fun changePump() {
mDeviceAddress = sp.getString(info.nightscout.pump.dana.R.string.key_danars_address, "")
mDeviceName = sp.getString(info.nightscout.pump.dana.R.string.key_danars_name, "")
danaPump.serialNumber = sp.getString(info.nightscout.pump.dana.R.string.key_danars_name, "")
danaPump.reset()
commandQueue.readStatus(rh.gs(info.nightscout.core.ui.R.string.device_changed), null)
}
@ -306,7 +307,6 @@ class DanaRSPlugin @Inject constructor(
val result = PumpEnactResult(injector)
result.success = connectionOK && abs(detailedBolusInfo.insulin - t.insulin) < pumpDescription.bolusStep
result.bolusDelivered = t.insulin
result.carbsDelivered = detailedBolusInfo.carbs
if (!result.success) {
var error = "" + danaPump.bolusStartErrorCode
when (danaPump.bolusStartErrorCode) {
@ -323,7 +323,6 @@ class DanaRSPlugin @Inject constructor(
val result = PumpEnactResult(injector)
result.success = false
result.bolusDelivered = 0.0
result.carbsDelivered = 0.0
result.comment = rh.gs(info.nightscout.core.ui.R.string.invalid_input)
aapsLogger.error("deliverTreatment: Invalid input")
result

Some files were not shown because too many files have changed in this diff Show more