Merge pull request #2237 from pzadroga/feature/new-sms-command

Add new SMS command - LOOP LGS/CLOSED
This commit is contained in:
Milos Kozak 2022-12-15 09:06:07 +01:00 committed by GitHub
commit fee7059559
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 167 additions and 35 deletions

View file

@ -11,6 +11,7 @@ import info.nightscout.androidaps.insight.database.InsightDatabaseDao
import info.nightscout.androidaps.insight.database.InsightDbHelper import info.nightscout.androidaps.insight.database.InsightDbHelper
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin
import info.nightscout.database.impl.AppRepository import info.nightscout.database.impl.AppRepository
import info.nightscout.interfaces.ApsMode
import info.nightscout.implementation.iob.GlucoseStatusProviderImpl import info.nightscout.implementation.iob.GlucoseStatusProviderImpl
import info.nightscout.interfaces.bgQualityCheck.BgQualityCheck import info.nightscout.interfaces.bgQualityCheck.BgQualityCheck
import info.nightscout.interfaces.constraints.Constraint import info.nightscout.interfaces.constraints.Constraint
@ -274,13 +275,13 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
// 2x Safety & Objectives // 2x Safety & Objectives
@Test @Test
fun isClosedLoopAllowedTest() { fun isClosedLoopAllowedTest() {
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, "open")).thenReturn("closed") `when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name)).thenReturn(ApsMode.CLOSED.name)
objectivesPlugin.objectives[Objectives.MAXIOB_ZERO_CL_OBJECTIVE].startedOn = 0 objectivesPlugin.objectives[Objectives.MAXIOB_ZERO_CL_OBJECTIVE].startedOn = 0
var c: Constraint<Boolean> = constraintChecker.isClosedLoopAllowed() var c: Constraint<Boolean> = constraintChecker.isClosedLoopAllowed()
aapsLogger.debug("Reason list: " + c.reasonList.toString()) aapsLogger.debug("Reason list: " + c.reasonList.toString())
// Assertions.assertTrue(c.reasonList[0].toString().contains("Closed loop is disabled")) // Safety & Objectives // Assertions.assertTrue(c.reasonList[0].toString().contains("Closed loop is disabled")) // Safety & Objectives
Assertions.assertEquals(false, c.value()) Assertions.assertEquals(false, c.value())
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, "open")).thenReturn("open") `when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name)).thenReturn(ApsMode.OPEN.name)
c = constraintChecker.isClosedLoopAllowed() c = constraintChecker.isClosedLoopAllowed()
Assertions.assertTrue(c.reasonList[0].contains("Closed loop mode disabled in preferences")) // Safety & Objectives 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(3, c.reasonList.size) // 2x Safety & Objectives
@ -323,7 +324,7 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
openAPSSMBPlugin.setPluginEnabled(PluginType.APS, true) openAPSSMBPlugin.setPluginEnabled(PluginType.APS, true)
objectivesPlugin.objectives[Objectives.SMB_OBJECTIVE].startedOn = 0 objectivesPlugin.objectives[Objectives.SMB_OBJECTIVE].startedOn = 0
`when`(sp.getBoolean(info.nightscout.plugins.aps.R.string.key_use_smb, false)).thenReturn(false) `when`(sp.getBoolean(info.nightscout.plugins.aps.R.string.key_use_smb, false)).thenReturn(false)
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, "open")).thenReturn("open") `when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name)).thenReturn(ApsMode.OPEN.name)
// `when`(constraintChecker.isClosedLoopAllowed()).thenReturn(Constraint(true)) // `when`(constraintChecker.isClosedLoopAllowed()).thenReturn(Constraint(true))
val c = constraintChecker.isSMBModeEnabled() val c = constraintChecker.isSMBModeEnabled()
Assertions.assertEquals(true, c.reasonList.size == 3) // 2x Safety & Objectives Assertions.assertEquals(true, c.reasonList.size == 3) // 2x Safety & Objectives
@ -430,7 +431,7 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
@Test @Test
fun iobAMAShouldBeLimited() { fun iobAMAShouldBeLimited() {
// No limit by default // No limit by default
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, "open")).thenReturn("closed") `when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name)).thenReturn(ApsMode.CLOSED.name)
`when`(sp.getDouble(info.nightscout.plugins.aps.R.string.key_openapsma_max_iob, 1.5)).thenReturn(1.5) `when`(sp.getDouble(info.nightscout.plugins.aps.R.string.key_openapsma_max_iob, 1.5)).thenReturn(1.5)
`when`(sp.getString(info.nightscout.core.utils.R.string.key_age, "")).thenReturn("teenage") `when`(sp.getString(info.nightscout.core.utils.R.string.key_age, "")).thenReturn("teenage")
openAPSAMAPlugin.setPluginEnabled(PluginType.APS, true) openAPSAMAPlugin.setPluginEnabled(PluginType.APS, true)
@ -446,7 +447,7 @@ class ConstraintsCheckerTest : TestBaseWithProfile() {
@Test @Test
fun iobSMBShouldBeLimited() { fun iobSMBShouldBeLimited() {
// No limit by default // No limit by default
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, "open")).thenReturn("closed") `when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name)).thenReturn(ApsMode.CLOSED.name)
`when`(sp.getDouble(info.nightscout.plugins.aps.R.string.key_openapssmb_max_iob, 3.0)).thenReturn(3.0) `when`(sp.getDouble(info.nightscout.plugins.aps.R.string.key_openapssmb_max_iob, 3.0)).thenReturn(3.0)
`when`(sp.getString(info.nightscout.core.utils.R.string.key_age, "")).thenReturn("teenage") `when`(sp.getString(info.nightscout.core.utils.R.string.key_age, "")).thenReturn("teenage")
openAPSSMBPlugin.setPluginEnabled(PluginType.APS, true) openAPSSMBPlugin.setPluginEnabled(PluginType.APS, true)

View file

@ -7,6 +7,7 @@ import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.TestBase import info.nightscout.androidaps.TestBase
import info.nightscout.core.utils.fabric.FabricPrivacy import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.database.impl.AppRepository import info.nightscout.database.impl.AppRepository
import info.nightscout.interfaces.ApsMode
import info.nightscout.interfaces.Config import info.nightscout.interfaces.Config
import info.nightscout.interfaces.configBuilder.RunningConfiguration import info.nightscout.interfaces.configBuilder.RunningConfiguration
import info.nightscout.interfaces.constraints.Constraints import info.nightscout.interfaces.constraints.Constraints
@ -70,7 +71,7 @@ class LoopPluginTest : TestBase() {
fun testPluginInterface() { fun testPluginInterface() {
`when`(rh.gs(info.nightscout.core.ui.R.string.loop)).thenReturn("Loop") `when`(rh.gs(info.nightscout.core.ui.R.string.loop)).thenReturn("Loop")
`when`(rh.gs(info.nightscout.plugins.aps.R.string.loop_shortname)).thenReturn("LOOP") `when`(rh.gs(info.nightscout.plugins.aps.R.string.loop_shortname)).thenReturn("LOOP")
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, "open")).thenReturn("closed") `when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name)).thenReturn(ApsMode.CLOSED.name)
val pumpDescription = PumpDescription() val pumpDescription = PumpDescription()
`when`(virtualPumpPlugin.pumpDescription).thenReturn(pumpDescription) `when`(virtualPumpPlugin.pumpDescription).thenReturn(pumpDescription)
Assert.assertEquals(LoopFragment::class.java.name, loopPlugin.pluginDescription.fragmentClass) Assert.assertEquals(LoopFragment::class.java.name, loopPlugin.pluginDescription.fragmentClass)

View file

@ -5,6 +5,7 @@ import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.HardLimitsMock import info.nightscout.androidaps.HardLimitsMock
import info.nightscout.androidaps.TestBaseWithProfile import info.nightscout.androidaps.TestBaseWithProfile
import info.nightscout.database.impl.AppRepository import info.nightscout.database.impl.AppRepository
import info.nightscout.interfaces.ApsMode
import info.nightscout.interfaces.Constants import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.bgQualityCheck.BgQualityCheck import info.nightscout.interfaces.bgQualityCheck.BgQualityCheck
import info.nightscout.interfaces.constraints.Constraint import info.nightscout.interfaces.constraints.Constraint
@ -98,7 +99,7 @@ class SafetyPluginTest : TestBaseWithProfile() {
@Test @Test
fun disabledEngineeringModeShouldLimitClosedLoop() { fun disabledEngineeringModeShouldLimitClosedLoop() {
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, "open")).thenReturn("closed") `when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name)).thenReturn(ApsMode.CLOSED.name)
`when`(config.isEngineeringModeOrRelease()).thenReturn(false) `when`(config.isEngineeringModeOrRelease()).thenReturn(false)
var c = Constraint(true) var c = Constraint(true)
c = safetyPlugin.isClosedLoopAllowed(c) c = safetyPlugin.isClosedLoopAllowed(c)
@ -108,7 +109,7 @@ class SafetyPluginTest : TestBaseWithProfile() {
@Test @Test
fun setOpenLoopInPreferencesShouldLimitClosedLoop() { fun setOpenLoopInPreferencesShouldLimitClosedLoop() {
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, "open")).thenReturn("open") `when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name)).thenReturn(ApsMode.OPEN.name)
var c = Constraint(true) var c = Constraint(true)
c = safetyPlugin.isClosedLoopAllowed(c) c = safetyPlugin.isClosedLoopAllowed(c)
Assertions.assertTrue(c.getReasons(aapsLogger).contains("Closed loop mode disabled in preferences")) Assertions.assertTrue(c.getReasons(aapsLogger).contains("Closed loop mode disabled in preferences"))
@ -278,7 +279,7 @@ Safety: Limiting max basal rate to 500.00 U/h because of pump limit
openAPSSMBPlugin.setPluginEnabled(PluginType.APS, true) openAPSSMBPlugin.setPluginEnabled(PluginType.APS, true)
//`when`(openAPSSMBPlugin.isEnabled()).thenReturn(true) //`when`(openAPSSMBPlugin.isEnabled()).thenReturn(true)
//`when`(openAPSAMAPlugin.isEnabled()).thenReturn(false) //`when`(openAPSAMAPlugin.isEnabled()).thenReturn(false)
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, "open")).thenReturn("lgs") `when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name)).thenReturn(ApsMode.LGS.name)
`when`(sp.getDouble(info.nightscout.plugins.aps.R.string.key_openapsma_max_iob, 1.5)).thenReturn(1.5) `when`(sp.getDouble(info.nightscout.plugins.aps.R.string.key_openapsma_max_iob, 1.5)).thenReturn(1.5)
`when`(sp.getDouble(info.nightscout.plugins.aps.R.string.key_openapssmb_max_iob, 3.0)).thenReturn(3.0) `when`(sp.getDouble(info.nightscout.plugins.aps.R.string.key_openapssmb_max_iob, 3.0)).thenReturn(3.0)
`when`(sp.getString(info.nightscout.core.utils.R.string.key_age, "")).thenReturn("teenage") `when`(sp.getString(info.nightscout.core.utils.R.string.key_age, "")).thenReturn("teenage")

View file

@ -0,0 +1,13 @@
package info.nightscout.interfaces
enum class ApsMode() {
OPEN,
CLOSED,
LGS,
UNDEFINED;
companion object {
fun fromString(stringValue: String?) = values().firstOrNull { it.name == stringValue?.uppercase() } ?: UNDEFINED
}
}

View file

@ -31,6 +31,7 @@ import info.nightscout.database.impl.transactions.InsertAndCancelCurrentOfflineE
import info.nightscout.database.impl.transactions.InsertTherapyEventAnnouncementTransaction import info.nightscout.database.impl.transactions.InsertTherapyEventAnnouncementTransaction
import info.nightscout.interfaces.Config import info.nightscout.interfaces.Config
import info.nightscout.interfaces.Constants import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.ApsMode
import info.nightscout.interfaces.aps.APSResult import info.nightscout.interfaces.aps.APSResult
import info.nightscout.interfaces.aps.Loop import info.nightscout.interfaces.aps.Loop
import info.nightscout.interfaces.aps.Loop.LastRun import info.nightscout.interfaces.aps.Loop.LastRun
@ -182,10 +183,10 @@ class LoopPlugin @Inject constructor(
get() { get() {
val closedLoopEnabled = constraintChecker.isClosedLoopAllowed() val closedLoopEnabled = constraintChecker.isClosedLoopAllowed()
val maxIobAllowed = constraintChecker.getMaxIOBAllowed().value() val maxIobAllowed = constraintChecker.getMaxIOBAllowed().value()
val apsMode = sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, "open") val apsMode = ApsMode.fromString(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name))
val pump = activePlugin.activePump val pump = activePlugin.activePump
var isLGS = false var isLGS = false
if (!isSuspended && !pump.isSuspended()) if (closedLoopEnabled.value()) if (maxIobAllowed == HardLimits.MAX_IOB_LGS || apsMode == "lgs") isLGS = true if (!isSuspended && !pump.isSuspended()) if (closedLoopEnabled.value()) if (maxIobAllowed == HardLimits.MAX_IOB_LGS || apsMode == ApsMode.LGS) isLGS = true
return isLGS return isLGS
} }

View file

@ -1,6 +1,7 @@
package info.nightscout.plugins.constraints.objectives.objectives package info.nightscout.plugins.constraints.objectives.objectives
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.interfaces.ApsMode
import info.nightscout.interfaces.constraints.Constraints import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.plugins.constraints.R import info.nightscout.plugins.constraints.R
import info.nightscout.shared.utils.T import info.nightscout.shared.utils.T
@ -15,7 +16,7 @@ class Objective6(injector: HasAndroidInjector) : Objective(injector, "maxiob", R
tasks.add(MinimumDurationTask(this, T.days(1).msecs())) tasks.add(MinimumDurationTask(this, T.days(1).msecs()))
tasks.add( tasks.add(
object : Task(this, R.string.closedmodeenabled) { object : Task(this, R.string.closedmodeenabled) {
override fun isCompleted(): Boolean = sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, "open") == "closed" override fun isCompleted(): Boolean = ApsMode.fromString(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name)) == ApsMode.CLOSED
}) })
tasks.add( tasks.add(
object : Task(this, R.string.maxiobset) { object : Task(this, R.string.maxiobset) {

View file

@ -8,6 +8,7 @@ import info.nightscout.core.utils.extensions.storeDouble
import info.nightscout.core.utils.extensions.storeInt import info.nightscout.core.utils.extensions.storeInt
import info.nightscout.core.utils.extensions.storeString import info.nightscout.core.utils.extensions.storeString
import info.nightscout.interfaces.Config import info.nightscout.interfaces.Config
import info.nightscout.interfaces.ApsMode
import info.nightscout.interfaces.constraints.Constraint import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.constraints.Safety import info.nightscout.interfaces.constraints.Safety
@ -67,8 +68,8 @@ class SafetyPlugin @Inject constructor(
} }
override fun isClosedLoopAllowed(value: Constraint<Boolean>): Constraint<Boolean> { override fun isClosedLoopAllowed(value: Constraint<Boolean>): Constraint<Boolean> {
val mode = sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, "open") val mode = ApsMode.fromString(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name))
if (mode == "open") value.set(aapsLogger, false, rh.gs(R.string.closedmodedisabledinpreferences), this) if (mode == ApsMode.OPEN) value.set(aapsLogger, false, rh.gs(R.string.closedmodedisabledinpreferences), this)
if (!config.isEngineeringModeOrRelease()) { if (!config.isEngineeringModeOrRelease()) {
if (value.value()) { if (value.value()) {
uiInteraction.addNotification(Notification.TOAST_ALARM, rh.gs(R.string.closed_loop_disabled_on_dev_branch), Notification.NORMAL) uiInteraction.addNotification(Notification.TOAST_ALARM, rh.gs(R.string.closed_loop_disabled_on_dev_branch), Notification.NORMAL)
@ -159,8 +160,8 @@ class SafetyPlugin @Inject constructor(
} }
override fun applyMaxIOBConstraints(maxIob: Constraint<Double>): Constraint<Double> { override fun applyMaxIOBConstraints(maxIob: Constraint<Double>): Constraint<Double> {
val apsMode = sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, "open") val apsMode = ApsMode.fromString(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name))
if (apsMode == "lgs") maxIob.setIfSmaller(aapsLogger, HardLimits.MAX_IOB_LGS, rh.gs(info.nightscout.core.ui.R.string.limiting_iob, HardLimits.MAX_IOB_LGS, rh.gs(info.nightscout.core.ui.R.string.lowglucosesuspend)), this) if (apsMode == ApsMode.LGS) maxIob.setIfSmaller(aapsLogger, HardLimits.MAX_IOB_LGS, rh.gs(info.nightscout.core.ui.R.string.limiting_iob, HardLimits.MAX_IOB_LGS, rh.gs(info.nightscout.core.ui.R.string.lowglucosesuspend)), this)
return maxIob return maxIob
} }

View file

@ -29,6 +29,7 @@ import info.nightscout.database.impl.transactions.CancelCurrentOfflineEventIfAny
import info.nightscout.database.impl.transactions.CancelCurrentTemporaryTargetIfAnyTransaction import info.nightscout.database.impl.transactions.CancelCurrentTemporaryTargetIfAnyTransaction
import info.nightscout.database.impl.transactions.InsertAndCancelCurrentOfflineEventTransaction import info.nightscout.database.impl.transactions.InsertAndCancelCurrentOfflineEventTransaction
import info.nightscout.database.impl.transactions.InsertAndCancelCurrentTemporaryTargetTransaction import info.nightscout.database.impl.transactions.InsertAndCancelCurrentTemporaryTargetTransaction
import info.nightscout.interfaces.ApsMode
import info.nightscout.interfaces.Config import info.nightscout.interfaces.Config
import info.nightscout.interfaces.Constants import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.GlucoseUnit import info.nightscout.interfaces.GlucoseUnit
@ -125,7 +126,7 @@ class SmsCommunicatorPlugin @Inject constructor(
val commands = mapOf( val commands = mapOf(
"BG" to "BG", "BG" to "BG",
"LOOP" to "LOOP STOP/DISABLE/START/ENABLE/RESUME/STATUS\nLOOP SUSPEND 20", "LOOP" to "LOOP STOP/DISABLE/START/ENABLE/RESUME/STATUS/CLOSED/LGS\nLOOP SUSPEND 20",
"NSCLIENT" to "NSCLIENT RESTART", "NSCLIENT" to "NSCLIENT RESTART",
"PUMP" to "PUMP\nPUMP CONNECT\nPUMP DISCONNECT 30\n", "PUMP" to "PUMP\nPUMP CONNECT\nPUMP DISCONNECT 30\n",
"BASAL" to "BASAL STOP/CANCEL\nBASAL 0.3\nBASAL 0.3 20\nBASAL 30%\nBASAL 30% 20\n", "BASAL" to "BASAL STOP/CANCEL\nBASAL 0.3\nBASAL 0.3 20\nBASAL 30%\nBASAL 30% 20\n",
@ -419,7 +420,7 @@ class SmsCommunicatorPlugin @Inject constructor(
"STATUS" -> { "STATUS" -> {
val reply = if (loop.enabled) { val reply = if (loop.enabled) {
if (loop.isSuspended) rh.gs(R.string.sms_loop_suspended_for, loop.minutesToEndOfSuspend()) if (loop.isSuspended) rh.gs(R.string.sms_loop_suspended_for, loop.minutesToEndOfSuspend())
else rh.gs(R.string.smscommunicator_loop_is_enabled) else rh.gs(R.string.smscommunicator_loop_is_enabled) + " - " + getApsModeText()
} else } else
rh.gs(info.nightscout.core.ui.R.string.loopisdisabled) rh.gs(info.nightscout.core.ui.R.string.loopisdisabled)
sendSMS(Sms(receivedSms.phoneNumber, reply)) sendSMS(Sms(receivedSms.phoneNumber, reply))
@ -502,6 +503,36 @@ class SmsCommunicatorPlugin @Inject constructor(
} }
} }
"LGS" -> {
val passCode = generatePassCode()
val reply = rh.gs(R.string.smscommunicator_set_lgs_reply_with_code, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false) {
override fun run() {
uel.log(Action.LGS_LOOP_MODE, Sources.SMS)
sp.putString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.LGS.name)
rxBus.send(EventPreferenceChange(rh.gs(info.nightscout.core.ui.R.string.lowglucosesuspend)))
val replyText = rh.gs(R.string.smscommunicator_current_loop_mode, getApsModeText())
sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText))
}
})
}
"CLOSED" -> {
val passCode = generatePassCode()
val reply = rh.gs(R.string.smscommunicator_set_closed_loop_reply_with_code, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false) {
override fun run() {
uel.log(Action.CLOSED_LOOP_MODE, Sources.SMS)
sp.putString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.CLOSED.name)
rxBus.send(EventPreferenceChange(rh.gs(info.nightscout.core.ui.R.string.closedloop)))
val replyText = rh.gs(R.string.smscommunicator_current_loop_mode, getApsModeText())
sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText))
}
})
}
else -> sendSMS(Sms(receivedSms.phoneNumber, rh.gs(R.string.wrong_format))) else -> sendSMS(Sms(receivedSms.phoneNumber, rh.gs(R.string.wrong_format)))
} }
} }
@ -1251,4 +1282,12 @@ class SmsCommunicatorPlugin @Inject constructor(
knownNumbers.size > 1 knownNumbers.size > 1
} ?: false } ?: false
} }
private fun getApsModeText(): String =
when (ApsMode.fromString(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name))) {
ApsMode.OPEN -> rh.gs(info.nightscout.core.ui.R.string.openloop)
ApsMode.CLOSED -> rh.gs(info.nightscout.core.ui.R.string.closedloop)
ApsMode.LGS -> rh.gs(info.nightscout.core.ui.R.string.lowglucosesuspend)
else -> rh.gs(info.nightscout.core.ui.R.string.unknown)
}
} }

View file

@ -85,6 +85,9 @@
<string name="smscommunicator_message_body">Błędny tekst wiadomości</string> <string name="smscommunicator_message_body">Błędny tekst wiadomości</string>
<string name="smscommunicator_report_pump_unreachable_summary">Wyślij SMS, jeśli wyzwolone jest zdarzenie pompy nieosiągalnej</string> <string name="smscommunicator_report_pump_unreachable_summary">Wyślij SMS, jeśli wyzwolone jest zdarzenie pompy nieosiągalnej</string>
<string name="smscommunicator_pump_unreachable">Zgłoś nieosiągalną pompę</string> <string name="smscommunicator_pump_unreachable">Zgłoś nieosiągalną pompę</string>
<string name="smscommunicator_set_lgs_reply_with_code">Aby przełączyć pętlę w tryb LGS (zawieszenie przy niskiej glikemii) wprowadź kod %1$s</string>
<string name="smscommunicator_set_closed_loop_reply_with_code">Aby przełączyć pętlę w tryb pętli zamkniętej wprowadź kod %1$s</string>
<string name="smscommunicator_current_loop_mode">Obecny tryb pętli: %1$s</string>
<string name="wrong_format">Błędny format</string> <string name="wrong_format">Błędny format</string>
<string name="sms_actual_bg">BG:</string> <string name="sms_actual_bg">BG:</string>
<string name="sms_last_bg">Ostatnia BG:</string> <string name="sms_last_bg">Ostatnia BG:</string>

View file

@ -95,6 +95,9 @@
<string name="smscommunicator_message_body">Invalid message body</string> <string name="smscommunicator_message_body">Invalid message body</string>
<string name="smscommunicator_report_pump_unreachable_summary">Send SMS if unreachable pump event is triggered</string> <string name="smscommunicator_report_pump_unreachable_summary">Send SMS if unreachable pump event is triggered</string>
<string name="smscommunicator_pump_unreachable">Report pump unreachable</string> <string name="smscommunicator_pump_unreachable">Report pump unreachable</string>
<string name="smscommunicator_set_lgs_reply_with_code">In order to switch Loop mode to LGS (Low Glucose Suspend) reply with code %1$s</string>
<string name="smscommunicator_set_closed_loop_reply_with_code">In order to switch Loop mode to Closed loop reply with code %1$s</string>
<string name="smscommunicator_current_loop_mode">Current loop mode: %1$s</string>
<string name="wrong_format">Wrong format</string> <string name="wrong_format">Wrong format</string>
<string name="sms_actual_bg">BG:</string> <string name="sms_actual_bg">BG:</string>
<string name="sms_last_bg">Last BG:</string> <string name="sms_last_bg">Last BG:</string>

View file

@ -11,6 +11,7 @@ import info.nightscout.database.impl.transactions.CancelCurrentOfflineEventIfAny
import info.nightscout.database.impl.transactions.InsertAndCancelCurrentOfflineEventTransaction import info.nightscout.database.impl.transactions.InsertAndCancelCurrentOfflineEventTransaction
import info.nightscout.database.impl.transactions.InsertAndCancelCurrentTemporaryTargetTransaction import info.nightscout.database.impl.transactions.InsertAndCancelCurrentTemporaryTargetTransaction
import info.nightscout.database.impl.transactions.Transaction import info.nightscout.database.impl.transactions.Transaction
import info.nightscout.interfaces.ApsMode
import info.nightscout.implementation.iob.GlucoseStatusProviderImpl import info.nightscout.implementation.iob.GlucoseStatusProviderImpl
import info.nightscout.interfaces.Constants import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.GlucoseUnit import info.nightscout.interfaces.GlucoseUnit
@ -85,6 +86,10 @@ class SmsCommunicatorPluginTest : TestBaseWithProfile() {
private lateinit var smsCommunicatorPlugin: SmsCommunicatorPlugin private lateinit var smsCommunicatorPlugin: SmsCommunicatorPlugin
private var hasBeenRun = false private var hasBeenRun = false
private val modeClosed = "Closed Loop"
private val modeOpen = "Open Loop"
private val modeLgs = "Low Glucose Suspend"
private val modeUnknown = "unknown"
@BeforeEach fun prepareTests() { @BeforeEach fun prepareTests() {
val reading = GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = 1514766900000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT) val reading = GlucoseValue(raw = 0.0, noise = 0.0, value = 100.0, timestamp = 1514766900000, sourceSensor = GlucoseValue.SourceSensor.UNKNOWN, trendArrow = GlucoseValue.TrendArrow.FLAT)
@ -248,7 +253,13 @@ class SmsCommunicatorPluginTest : TestBaseWithProfile() {
`when`(rh.gsNotLocalised(R.string.smscommunicator_tempbasal_canceled)).thenReturn("Temp basal canceled") `when`(rh.gsNotLocalised(R.string.smscommunicator_tempbasal_canceled)).thenReturn("Temp basal canceled")
`when`(rh.gsNotLocalised(R.string.smscommunicator_calibration_sent)).thenReturn("Calibration sent. Receiving must be enabled in xDrip+.") `when`(rh.gsNotLocalised(R.string.smscommunicator_calibration_sent)).thenReturn("Calibration sent. Receiving must be enabled in xDrip+.")
`when`(rh.gsNotLocalised(R.string.smscommunicator_tt_canceled)).thenReturn("Temp Target canceled successfully") `when`(rh.gsNotLocalised(R.string.smscommunicator_tt_canceled)).thenReturn("Temp Target canceled successfully")
`when`(rh.gs(info.nightscout.core.ui.R.string.closedloop)).thenReturn(modeClosed)
`when`(rh.gs(info.nightscout.core.ui.R.string.openloop)).thenReturn(modeOpen)
`when`(rh.gs(info.nightscout.core.ui.R.string.lowglucosesuspend)).thenReturn(modeLgs)
`when`(rh.gs(info.nightscout.core.ui.R.string.unknown)).thenReturn(modeUnknown)
`when`(rh.gs(R.string.smscommunicator_set_closed_loop_reply_with_code)).thenReturn("In order to switch Loop mode to Closed loop reply with code %1\$s")
`when`(rh.gs(R.string.smscommunicator_current_loop_mode)).thenReturn("Current loop mode: %1\$s")
`when`(rh.gs(R.string.smscommunicator_set_lgs_reply_with_code)).thenReturn("In order to switch Loop mode to LGS (Low Glucose Suspend) reply with code %1\$s")
} }
@Test @Test
@ -329,15 +340,40 @@ class SmsCommunicatorPluginTest : TestBaseWithProfile() {
Assertions.assertEquals("LOOP STATUS", smsCommunicatorPlugin.messages[0].text) Assertions.assertEquals("LOOP STATUS", smsCommunicatorPlugin.messages[0].text)
Assertions.assertEquals("Suspended (10 m)", smsCommunicatorPlugin.messages[1].text) Assertions.assertEquals("Suspended (10 m)", smsCommunicatorPlugin.messages[1].text)
//LOOP STATUS : enabled //LOOP STATUS : enabled - APS mode - Closed
`when`(loop.enabled).thenReturn(true) `when`(loop.enabled).thenReturn(true)
`when`(loop.isSuspended).thenReturn(false) `when`(loop.isSuspended).thenReturn(false)
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name)).thenReturn(ApsMode.CLOSED.name)
smsCommunicatorPlugin.messages = ArrayList() smsCommunicatorPlugin.messages = ArrayList()
sms = Sms("1234", "LOOP STATUS") sms = Sms("1234", "LOOP STATUS")
smsCommunicatorPlugin.processSms(sms) smsCommunicatorPlugin.processSms(sms)
Assertions.assertFalse(sms.ignored) Assertions.assertFalse(sms.ignored)
Assertions.assertEquals("LOOP STATUS", smsCommunicatorPlugin.messages[0].text) Assertions.assertEquals("LOOP STATUS", smsCommunicatorPlugin.messages[0].text)
Assertions.assertEquals("Loop is enabled", smsCommunicatorPlugin.messages[1].text) Assertions.assertEquals("Loop is enabled - $modeClosed", smsCommunicatorPlugin.messages[1].text)
//LOOP STATUS : enabled - APS mode - Open
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name)).thenReturn(ApsMode.OPEN.name)
smsCommunicatorPlugin.messages = ArrayList()
smsCommunicatorPlugin.processSms(sms)
Assertions.assertFalse(sms.ignored)
Assertions.assertEquals("LOOP STATUS", smsCommunicatorPlugin.messages[0].text)
Assertions.assertEquals("Loop is enabled - $modeOpen", smsCommunicatorPlugin.messages[1].text)
//LOOP STATUS : enabled - APS mode - LGS
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name)).thenReturn(ApsMode.LGS.name)
smsCommunicatorPlugin.messages = ArrayList()
smsCommunicatorPlugin.processSms(sms)
Assertions.assertFalse(sms.ignored)
Assertions.assertEquals("LOOP STATUS", smsCommunicatorPlugin.messages[0].text)
Assertions.assertEquals("Loop is enabled - $modeLgs", smsCommunicatorPlugin.messages[1].text)
//LOOP STATUS : enabled - APS mode - unknown
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name)).thenReturn("some wrong value")
smsCommunicatorPlugin.messages = ArrayList()
smsCommunicatorPlugin.processSms(sms)
Assertions.assertFalse(sms.ignored)
Assertions.assertEquals("LOOP STATUS", smsCommunicatorPlugin.messages[0].text)
Assertions.assertEquals("Loop is enabled - $modeUnknown", smsCommunicatorPlugin.messages[1].text)
//LOOP : wrong format //LOOP : wrong format
`when`(loop.enabled).thenReturn(true) `when`(loop.enabled).thenReturn(true)
@ -480,6 +516,37 @@ class SmsCommunicatorPluginTest : TestBaseWithProfile() {
Assertions.assertEquals("LOOP BLABLA", smsCommunicatorPlugin.messages[0].text) Assertions.assertEquals("LOOP BLABLA", smsCommunicatorPlugin.messages[0].text)
Assertions.assertEquals("Wrong format", smsCommunicatorPlugin.messages[1].text) Assertions.assertEquals("Wrong format", smsCommunicatorPlugin.messages[1].text)
//LOOP CLOSED
var smsCommand = "LOOP CLOSED"
val replyClosed = "In order to switch Loop mode to Closed loop reply with code "
`when`(loop.enabled).thenReturn(true)
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name)).thenReturn(ApsMode.CLOSED.name)
smsCommunicatorPlugin.messages = ArrayList()
sms = Sms("1234", smsCommand)
smsCommunicatorPlugin.processSms(sms)
Assertions.assertFalse(sms.ignored)
Assertions.assertEquals(smsCommand, smsCommunicatorPlugin.messages[0].text)
Assertions.assertTrue(smsCommunicatorPlugin.messages[1].text.contains(replyClosed))
passCode = smsCommunicatorPlugin.messageToConfirm?.confirmCode!!
smsCommunicatorPlugin.processSms(Sms("1234", passCode))
Assertions.assertEquals(passCode, smsCommunicatorPlugin.messages[2].text)
Assertions.assertEquals("Current loop mode: $modeClosed", smsCommunicatorPlugin.messages[3].text)
//LOOP LGS
smsCommand = "LOOP LGS"
val replyLgs = "In order to switch Loop mode to LGS (Low Glucose Suspend) reply with code "
`when`(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name)).thenReturn(ApsMode.LGS.name)
smsCommunicatorPlugin.messages = ArrayList()
sms = Sms("1234", smsCommand)
smsCommunicatorPlugin.processSms(sms)
Assertions.assertFalse(sms.ignored)
Assertions.assertEquals(smsCommand, smsCommunicatorPlugin.messages[0].text)
Assertions.assertTrue(smsCommunicatorPlugin.messages[1].text.contains(replyLgs))
passCode = smsCommunicatorPlugin.messageToConfirm?.confirmCode!!
smsCommunicatorPlugin.processSms(Sms("1234", passCode))
Assertions.assertEquals(passCode, smsCommunicatorPlugin.messages[2].text)
Assertions.assertEquals("Current loop mode: $modeLgs", smsCommunicatorPlugin.messages[3].text)
//NSCLIENT RESTART //NSCLIENT RESTART
`when`(loop.isEnabled()).thenReturn(true) `when`(loop.isEnabled()).thenReturn(true)
`when`(loop.isSuspended).thenReturn(false) `when`(loop.isSuspended).thenReturn(false)

View file

@ -21,6 +21,7 @@ import info.nightscout.database.impl.AppRepository
import info.nightscout.database.impl.transactions.CancelCurrentOfflineEventIfAnyTransaction import info.nightscout.database.impl.transactions.CancelCurrentOfflineEventIfAnyTransaction
import info.nightscout.database.impl.transactions.InsertAndCancelCurrentOfflineEventTransaction import info.nightscout.database.impl.transactions.InsertAndCancelCurrentOfflineEventTransaction
import info.nightscout.interfaces.ConfigBuilder import info.nightscout.interfaces.ConfigBuilder
import info.nightscout.interfaces.ApsMode
import info.nightscout.interfaces.aps.Loop import info.nightscout.interfaces.aps.Loop
import info.nightscout.interfaces.constraints.Constraint import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints import info.nightscout.interfaces.constraints.Constraints
@ -161,7 +162,7 @@ class LoopDialog : DaggerDialogFragment() {
val closedLoopAllowed = constraintChecker.isClosedLoopAllowed(Constraint(true)) val closedLoopAllowed = constraintChecker.isClosedLoopAllowed(Constraint(true))
val closedLoopAllowed2 = activePlugin.activeObjectives?.isAccomplished(Objectives.MAXIOB_OBJECTIVE) ?: false val closedLoopAllowed2 = activePlugin.activeObjectives?.isAccomplished(Objectives.MAXIOB_OBJECTIVE) ?: false
val lgsEnabled = constraintChecker.isLgsAllowed(Constraint(true)) val lgsEnabled = constraintChecker.isLgsAllowed(Constraint(true))
val apsMode = sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, "open") val apsMode = ApsMode.fromString(sp.getString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name))
val pump = activePlugin.activePump val pump = activePlugin.activePump
binding.overviewDisconnect15m.visibility = pumpDescription.tempDurationStep15mAllowed.toVisibility() binding.overviewDisconnect15m.visibility = pumpDescription.tempDurationStep15mAllowed.toVisibility()
@ -210,20 +211,20 @@ class LoopDialog : DaggerDialogFragment() {
else -> { else -> {
binding.overviewLoop.visibility = View.VISIBLE binding.overviewLoop.visibility = View.VISIBLE
binding.overviewEnable.visibility = View.GONE binding.overviewEnable.visibility = View.GONE
when { when (apsMode) {
apsMode == "closed" -> { ApsMode.CLOSED -> {
binding.overviewCloseloop.visibility = View.GONE binding.overviewCloseloop.visibility = View.GONE
binding.overviewLgsloop.visibility = View.VISIBLE binding.overviewLgsloop.visibility = View.VISIBLE
binding.overviewOpenloop.visibility = View.VISIBLE binding.overviewOpenloop.visibility = View.VISIBLE
} }
apsMode == "lgs" -> { ApsMode.LGS -> {
binding.overviewCloseloop.visibility = closedLoopAllowed.value().toVisibility() //show Close loop button only if Close loop allowed binding.overviewCloseloop.visibility = closedLoopAllowed.value().toVisibility() //show Close loop button only if Close loop allowed
binding.overviewLgsloop.visibility = View.GONE binding.overviewLgsloop.visibility = View.GONE
binding.overviewOpenloop.visibility = View.VISIBLE binding.overviewOpenloop.visibility = View.VISIBLE
} }
apsMode == "open" -> { ApsMode.OPEN -> {
binding.overviewCloseloop.visibility = binding.overviewCloseloop.visibility =
closedLoopAllowed2.toVisibility() //show CloseLoop button only if Objective 6 is completed (closedLoopAllowed always false in open loop mode) closedLoopAllowed2.toVisibility() //show CloseLoop button only if Objective 6 is completed (closedLoopAllowed always false in open loop mode)
binding.overviewLgsloop.visibility = lgsEnabled.value().toVisibility() binding.overviewLgsloop.visibility = lgsEnabled.value().toVisibility()
@ -282,21 +283,21 @@ class LoopDialog : DaggerDialogFragment() {
when (v.id) { when (v.id) {
R.id.overview_closeloop -> { R.id.overview_closeloop -> {
uel.log(UserEntry.Action.CLOSED_LOOP_MODE, UserEntry.Sources.LoopDialog) uel.log(UserEntry.Action.CLOSED_LOOP_MODE, UserEntry.Sources.LoopDialog)
sp.putString(info.nightscout.core.utils.R.string.key_aps_mode, "closed") sp.putString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.CLOSED.name)
rxBus.send(EventPreferenceChange(rh.gs(info.nightscout.core.ui.R.string.closedloop))) rxBus.send(EventPreferenceChange(rh.gs(info.nightscout.core.ui.R.string.closedloop)))
return true return true
} }
R.id.overview_lgsloop -> { R.id.overview_lgsloop -> {
uel.log(UserEntry.Action.LGS_LOOP_MODE, UserEntry.Sources.LoopDialog) uel.log(UserEntry.Action.LGS_LOOP_MODE, UserEntry.Sources.LoopDialog)
sp.putString(info.nightscout.core.utils.R.string.key_aps_mode, "lgs") sp.putString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.LGS.name)
rxBus.send(EventPreferenceChange(rh.gs(info.nightscout.core.ui.R.string.lowglucosesuspend))) rxBus.send(EventPreferenceChange(rh.gs(info.nightscout.core.ui.R.string.lowglucosesuspend)))
return true return true
} }
R.id.overview_openloop -> { R.id.overview_openloop -> {
uel.log(UserEntry.Action.OPEN_LOOP_MODE, UserEntry.Sources.LoopDialog) uel.log(UserEntry.Action.OPEN_LOOP_MODE, UserEntry.Sources.LoopDialog)
sp.putString(info.nightscout.core.utils.R.string.key_aps_mode, "open") sp.putString(info.nightscout.core.utils.R.string.key_aps_mode, ApsMode.OPEN.name)
rxBus.send(EventPreferenceChange(rh.gs(info.nightscout.core.ui.R.string.lowglucosesuspend))) rxBus.send(EventPreferenceChange(rh.gs(info.nightscout.core.ui.R.string.lowglucosesuspend)))
return true return true
} }