Merge pull request #119 from 0pen-dash/revert-118-revert-117-avereha/merge-dev-2

Revert "Revert "avereha/merge dev 2""
This commit is contained in:
Andrei Vereha 2021-09-15 18:38:54 +02:00 committed by GitHub
commit 7af3685933
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 224 additions and 134 deletions

View file

@ -1,42 +1,7 @@
package info.nightscout.androidaps package info.nightscout.androidaps
import android.os.SystemClock
import android.view.View
import android.view.ViewGroup
import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.scrollTo
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withClassName
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withTagValue
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
import androidx.test.rule.GrantPermissionRule
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin
import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin
import info.nightscout.androidaps.plugins.source.RandomBgPlugin
import info.nightscout.androidaps.setupwizard.SetupWizardActivity
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.extensions.isRunningTest
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers
import org.hamcrest.TypeSafeMatcher
import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@LargeTest @LargeTest

View file

@ -8,14 +8,18 @@ import android.widget.ArrayAdapter
import com.google.common.base.Joiner import com.google.common.base.Joiner
import info.nightscout.androidaps.Constants import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.ProfileSealed
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.databinding.DialogProfileswitchBinding import info.nightscout.androidaps.databinding.DialogProfileswitchBinding
import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.HtmlHelper import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
@ -31,6 +35,9 @@ class ProfileSwitchDialog : DialogFragmentWithDate() {
@Inject lateinit var activePlugin: ActivePlugin @Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var repository: AppRepository @Inject lateinit var repository: AppRepository
@Inject lateinit var uel: UserEntryLogger @Inject lateinit var uel: UserEntryLogger
@Inject lateinit var config: Config
@Inject lateinit var hardLimits: HardLimits
@Inject lateinit var rxBus: RxBusWrapper
private var profileIndex: Int? = null private var profileIndex: Int? = null
@ -129,22 +136,33 @@ class ProfileSwitchDialog : DialogFragmentWithDate() {
actions.add(resourceHelper.gs(R.string.time) + ": " + dateUtil.dateAndTimeString(eventTime)) actions.add(resourceHelper.gs(R.string.time) + ": " + dateUtil.dateAndTimeString(eventTime))
activity?.let { activity -> activity?.let { activity ->
OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.careportal_profileswitch), HtmlHelper.fromHtml(Joiner.on("<br/>").join(actions)), { val ps = profileFunction.buildProfileSwitch(profileStore, profileName, duration, percent, timeShift, eventTime)
profileFunction.createProfileSwitch(profileStore, val validity = ProfileSealed.PS(ps).isValid(resourceHelper.gs(R.string.careportal_profileswitch), activePlugin.activePump, config, resourceHelper, rxBus, hardLimits)
profileName = profileName, if (validity.isValid)
durationInMinutes = duration, OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.careportal_profileswitch), HtmlHelper.fromHtml(Joiner.on("<br/>").join(actions)), {
percentage = percent, profileFunction.createProfileSwitch(profileStore,
timeShiftInHours = timeShift, profileName = profileName,
timestamp = eventTime) durationInMinutes = duration,
uel.log(Action.PROFILE_SWITCH, percentage = percent,
Sources.ProfileSwitchDialog, timeShiftInHours = timeShift,
notes, timestamp = eventTime)
ValueWithUnit.Timestamp(eventTime).takeIf { eventTimeChanged }, uel.log(Action.PROFILE_SWITCH,
ValueWithUnit.SimpleString(profileName), Sources.ProfileSwitchDialog,
ValueWithUnit.Percent(percent), notes,
ValueWithUnit.Hour(timeShift).takeIf { timeShift != 0 }, ValueWithUnit.Timestamp(eventTime).takeIf { eventTimeChanged },
ValueWithUnit.Minute(duration).takeIf { duration != 0 }) ValueWithUnit.SimpleString(profileName),
}) ValueWithUnit.Percent(percent),
ValueWithUnit.Hour(timeShift).takeIf { timeShift != 0 },
ValueWithUnit.Minute(duration).takeIf { duration != 0 })
})
else {
OKDialog.show(
activity,
resourceHelper.gs(R.string.careportal_profileswitch),
HtmlHelper.fromHtml(Joiner.on("<br/>").join(validity.reasons))
)
return false
}
} }
return true return true
} }

View file

@ -46,7 +46,6 @@ import info.nightscout.androidaps.plugins.general.wear.events.EventWearConfirmAc
import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
import info.nightscout.androidaps.queue.Callback import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.queue.commands.Command
import info.nightscout.androidaps.receivers.ReceiverStatusStore import info.nightscout.androidaps.receivers.ReceiverStatusStore
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy

View file

@ -125,11 +125,11 @@ class OpenAPSAMAPlugin @Inject constructor(
maxBg = hardLimits.verifyHardLimits(tempTarget.value.highTarget, R.string.temp_target_high_target, HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1].toDouble()) maxBg = hardLimits.verifyHardLimits(tempTarget.value.highTarget, R.string.temp_target_high_target, HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1].toDouble())
targetBg = hardLimits.verifyHardLimits(tempTarget.value.target(), R.string.temp_target_value, HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1].toDouble()) targetBg = hardLimits.verifyHardLimits(tempTarget.value.target(), R.string.temp_target_value, HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1].toDouble())
} }
if (!hardLimits.checkOnlyHardLimits(profile.dia, R.string.profile_dia, hardLimits.minDia(), hardLimits.maxDia())) return if (!hardLimits.checkHardLimits(profile.dia, R.string.profile_dia, hardLimits.minDia(), hardLimits.maxDia())) return
if (!hardLimits.checkOnlyHardLimits(profile.getIcTimeFromMidnight(Profile.secondsFromMidnight()), R.string.profile_carbs_ratio_value, hardLimits.minIC(), hardLimits.maxIC())) return if (!hardLimits.checkHardLimits(profile.getIcTimeFromMidnight(Profile.secondsFromMidnight()), R.string.profile_carbs_ratio_value, hardLimits.minIC(), hardLimits.maxIC())) return
if (!hardLimits.checkOnlyHardLimits(profile.getIsfMgdl(), R.string.profile_sensitivity_value, HardLimits.MIN_ISF, HardLimits.MAX_ISF)) return if (!hardLimits.checkHardLimits(profile.getIsfMgdl(), R.string.profile_sensitivity_value, HardLimits.MIN_ISF, HardLimits.MAX_ISF)) return
if (!hardLimits.checkOnlyHardLimits(profile.getMaxDailyBasal(), R.string.profile_max_daily_basal_value, 0.02, hardLimits.maxBasal())) return if (!hardLimits.checkHardLimits(profile.getMaxDailyBasal(), R.string.profile_max_daily_basal_value, 0.02, hardLimits.maxBasal())) return
if (!hardLimits.checkOnlyHardLimits(pump.baseBasalRate, R.string.current_basal_value, 0.01, hardLimits.maxBasal())) return if (!hardLimits.checkHardLimits(pump.baseBasalRate, R.string.current_basal_value, 0.01, hardLimits.maxBasal())) return
startPart = System.currentTimeMillis() startPart = System.currentTimeMillis()
if (constraintChecker.isAutosensModeEnabled().value()) { if (constraintChecker.isAutosensModeEnabled().value()) {
val autosensData = iobCobCalculator.getLastAutosensDataWithWaitForCalculationFinish("OpenAPSPlugin") val autosensData = iobCobCalculator.getLastAutosensDataWithWaitForCalculationFinish("OpenAPSPlugin")

View file

@ -130,11 +130,11 @@ class OpenAPSSMBPlugin @Inject constructor(
maxBg = hardLimits.verifyHardLimits(tempTarget.value.highTarget, R.string.temp_target_high_target, HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1].toDouble()) maxBg = hardLimits.verifyHardLimits(tempTarget.value.highTarget, R.string.temp_target_high_target, HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1].toDouble())
targetBg = hardLimits.verifyHardLimits(tempTarget.value.target(), R.string.temp_target_value, HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1].toDouble()) targetBg = hardLimits.verifyHardLimits(tempTarget.value.target(), R.string.temp_target_value, HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1].toDouble())
} }
if (!hardLimits.checkOnlyHardLimits(profile.dia, R.string.profile_dia, hardLimits.minDia(), hardLimits.maxDia())) return if (!hardLimits.checkHardLimits(profile.dia, R.string.profile_dia, hardLimits.minDia(), hardLimits.maxDia())) return
if (!hardLimits.checkOnlyHardLimits(profile.getIcTimeFromMidnight(Profile.secondsFromMidnight()), R.string.profile_carbs_ratio_value, hardLimits.minIC(), hardLimits.maxIC())) return if (!hardLimits.checkHardLimits(profile.getIcTimeFromMidnight(Profile.secondsFromMidnight()), R.string.profile_carbs_ratio_value, hardLimits.minIC(), hardLimits.maxIC())) return
if (!hardLimits.checkOnlyHardLimits(profile.getIsfMgdl(), R.string.profile_sensitivity_value, HardLimits.MIN_ISF, HardLimits.MAX_ISF)) return if (!hardLimits.checkHardLimits(profile.getIsfMgdl(), R.string.profile_sensitivity_value, HardLimits.MIN_ISF, HardLimits.MAX_ISF)) return
if (!hardLimits.checkOnlyHardLimits(profile.getMaxDailyBasal(), R.string.profile_max_daily_basal_value, 0.02, hardLimits.maxBasal())) return if (!hardLimits.checkHardLimits(profile.getMaxDailyBasal(), R.string.profile_max_daily_basal_value, 0.02, hardLimits.maxBasal())) return
if (!hardLimits.checkOnlyHardLimits(pump.baseBasalRate, R.string.current_basal_value, 0.01, hardLimits.maxBasal())) return if (!hardLimits.checkHardLimits(pump.baseBasalRate, R.string.current_basal_value, 0.01, hardLimits.maxBasal())) return
startPart = System.currentTimeMillis() startPart = System.currentTimeMillis()
if (constraintChecker.isAutosensModeEnabled().value()) { if (constraintChecker.isAutosensModeEnabled().value()) {
val autosensData = iobCobCalculator.getLastAutosensDataWithWaitForCalculationFinish("OpenAPSPlugin") val autosensData = iobCobCalculator.getLastAutosensDataWithWaitForCalculationFinish("OpenAPSPlugin")

View file

@ -91,10 +91,10 @@ class ProfileFunctionImplementation @Inject constructor(
if (sp.getString(R.string.key_units, Constants.MGDL) == Constants.MGDL) GlucoseUnit.MGDL if (sp.getString(R.string.key_units, Constants.MGDL) == Constants.MGDL) GlucoseUnit.MGDL
else GlucoseUnit.MMOL else GlucoseUnit.MMOL
override fun createProfileSwitch(profileStore: ProfileStore, profileName: String, durationInMinutes: Int, percentage: Int, timeShiftInHours: Int, timestamp: Long) { override fun buildProfileSwitch(profileStore: ProfileStore, profileName: String, durationInMinutes: Int, percentage: Int, timeShiftInHours: Int, timestamp: Long) : ProfileSwitch {
val pureProfile = profileStore.getSpecificProfile(profileName) val pureProfile = profileStore.getSpecificProfile(profileName)
?: throw InvalidParameterSpecException(profileName) ?: throw InvalidParameterSpecException(profileName)
val ps = ProfileSwitch( return ProfileSwitch(
timestamp = timestamp, timestamp = timestamp,
basalBlocks = pureProfile.basalBlocks, basalBlocks = pureProfile.basalBlocks,
isfBlocks = pureProfile.isfBlocks, isfBlocks = pureProfile.isfBlocks,
@ -105,8 +105,14 @@ class ProfileFunctionImplementation @Inject constructor(
timeshift = T.hours(timeShiftInHours.toLong()).msecs(), timeshift = T.hours(timeShiftInHours.toLong()).msecs(),
percentage = percentage, percentage = percentage,
duration = T.mins(durationInMinutes.toLong()).msecs(), duration = T.mins(durationInMinutes.toLong()).msecs(),
insulinConfiguration = activePlugin.activeInsulin.insulinConfiguration.also { it.insulinEndTime = (pureProfile.dia * 3600 * 1000).toLong() } insulinConfiguration = activePlugin.activeInsulin.insulinConfiguration.also {
it.insulinEndTime = (pureProfile.dia * 3600 * 1000).toLong()
}
) )
}
override fun createProfileSwitch(profileStore: ProfileStore, profileName: String, durationInMinutes: Int, percentage: Int, timeShiftInHours: Int, timestamp: Long) {
val ps = buildProfileSwitch(profileStore, profileName, durationInMinutes, percentage, timeShiftInHours, timestamp)
disposable += repository.runTransactionForResult(InsertOrUpdateProfileSwitch(ps)) disposable += repository.runTransactionForResult(InsertOrUpdateProfileSwitch(ps))
.subscribe({ result -> .subscribe({ result ->
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted ProfileSwitch $it") } result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted ProfileSwitch $it") }

View file

@ -740,7 +740,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
graphData.addTreatments() graphData.addTreatments()
if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal]) if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal])
graphData.addActivity(0.8) graphData.addActivity(0.8)
if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal]) if ((pump.pumpDescription.isTempBasalCapable || config.NSCLIENT) && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal])
graphData.addBasals() graphData.addBasals()
graphData.addTargetLine() graphData.addTargetLine()
graphData.addNowLine(dateUtil.now()) graphData.addNowLine(dateUtil.now())

View file

@ -28,6 +28,7 @@ import info.nightscout.androidaps.setupwizard.elements.*
import info.nightscout.androidaps.setupwizard.events.EventSWUpdate import info.nightscout.androidaps.setupwizard.events.EventSWUpdate
import info.nightscout.androidaps.utils.AndroidPermission import info.nightscout.androidaps.utils.AndroidPermission
import info.nightscout.androidaps.utils.CryptoUtil import info.nightscout.androidaps.utils.CryptoUtil
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.extensions.isRunningTest import info.nightscout.androidaps.utils.extensions.isRunningTest
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.sharedPreferences.SP
@ -53,7 +54,8 @@ class SWDefinition @Inject constructor(
private val importExportPrefs: ImportExportPrefs, private val importExportPrefs: ImportExportPrefs,
private val androidPermission: AndroidPermission, private val androidPermission: AndroidPermission,
private val cryptoUtil: CryptoUtil, private val cryptoUtil: CryptoUtil,
private val config: Config private val config: Config,
private val hardLimits: HardLimits
) { ) {
lateinit var activity: AppCompatActivity lateinit var activity: AppCompatActivity
@ -255,7 +257,7 @@ class SWDefinition @Inject constructor(
.add(SWFragment(injector, this) .add(SWFragment(injector, this)
.add(LocalProfileFragment())) .add(LocalProfileFragment()))
.validator { .validator {
localProfilePlugin.profile?.getDefaultProfile()?.let { ProfileSealed.Pure(it).isValid("StartupWizard", activePlugin.activePump, config, resourceHelper, rxBus) } localProfilePlugin.profile?.getDefaultProfile()?.let { ProfileSealed.Pure(it).isValid("StartupWizard", activePlugin.activePump, config, resourceHelper, rxBus, hardLimits).isValid }
?: false ?: false
} }
.visibility { localProfilePlugin.isEnabled() } .visibility { localProfilePlugin.isEnabled() }

View file

@ -250,8 +250,6 @@
<string name="smscommunicator_loophasbeendisabled">Loop has been disabled</string> <string name="smscommunicator_loophasbeendisabled">Loop has been disabled</string>
<string name="smscommunicator_loophasbeenenabled">Loop has been enabled</string> <string name="smscommunicator_loophasbeenenabled">Loop has been enabled</string>
<string name="smscommunicator_loopisenabled">Loop is enabled</string> <string name="smscommunicator_loopisenabled">Loop is enabled</string>
<string name="valuelimitedto">%1$.2f limited to %2$.2f</string>
<string name="valueoutofrange">»%1$s« is out of hard limits</string>
<string name="smscommunicator_pumpconnectwithcode">To connect pump reply with code %1$s</string> <string name="smscommunicator_pumpconnectwithcode">To connect pump reply with code %1$s</string>
<string name="smscommunicator_pumpconnectfail">Connection to pump failed</string> <string name="smscommunicator_pumpconnectfail">Connection to pump failed</string>
<string name="smscommunicator_pumpdisconnectwithcode">To disconnect pump for %1$d minutes reply with code %2$s</string> <string name="smscommunicator_pumpdisconnectwithcode">To disconnect pump for %1$d minutes reply with code %2$s</string>
@ -400,12 +398,6 @@
<string name="adult">Adult</string> <string name="adult">Adult</string>
<string name="resistantadult">Insulin resistant adult</string> <string name="resistantadult">Insulin resistant adult</string>
<string name="pregnant">Pregnancy</string> <string name="pregnant">Pregnancy</string>
<string name="key_age" translatable="false">age</string>
<string name="key_child" translatable="false">child</string>
<string name="key_teenage" translatable="false">teenage</string>
<string name="key_adult" translatable="false">adult</string>
<string name="key_resistantadult" translatable="false">resistantadult</string>
<string name="key_pregnant" translatable="false">pregnant</string>
<string name="patientage_summary">Please select patient type to setup safety limits</string> <string name="patientage_summary">Please select patient type to setup safety limits</string>
<string name="patient_name">Patient name</string> <string name="patient_name">Patient name</string>
<string name="patient_name_summary">Please provide patient name or nickname to differentiate among multiple setups</string> <string name="patient_name_summary">Please provide patient name or nickname to differentiate among multiple setups</string>
@ -1082,16 +1074,6 @@
<string name="email_address">Email address</string> <string name="email_address">Email address</string>
<string name="privacy_settings">Privacy setting</string> <string name="privacy_settings">Privacy setting</string>
<string name="privacy_summary">You can provide optional email address if you want to be notified about app crashes. This is not an automated service. You will be contacted by developers in dangerous situations.</string> <string name="privacy_summary">You can provide optional email address if you want to be notified about app crashes. This is not an automated service. You will be contacted by developers in dangerous situations.</string>
<string name="profile_low_target">Profile low target</string>
<string name="profile_high_target">Profile high target</string>
<string name="temp_target_low_target">Temporary target bottom value</string>
<string name="temp_target_high_target">Temporary target top value</string>
<string name="temp_target_value">Temporary target value</string>
<string name="profile_dia">Profile DIA value</string>
<string name="profile_sensitivity_value">Profile sensitivity value</string>
<string name="profile_max_daily_basal_value">Maximal profile basal value</string>
<string name="current_basal_value">Current basal value</string>
<string name="profile_carbs_ratio_value">Profile carbs ratio value</string>
<string name="full_sync">Full sync</string> <string name="full_sync">Full sync</string>
<string name="prime">Prime</string> <string name="prime">Prime</string>
<string name="ns_sync_options">Synchronization</string> <string name="ns_sync_options">Synchronization</string>

View file

@ -19,6 +19,8 @@ import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import org.json.JSONArray import org.json.JSONArray
@ -94,34 +96,93 @@ sealed class ProfileSealed(
value.timeZone.rawOffset.toLong() value.timeZone.rawOffset.toLong()
) )
override fun isValid(from: String, pump: Pump, config: Config, resourceHelper: ResourceHelper, rxBus: RxBusWrapper): Boolean { override fun isValid(from: String, pump: Pump, config: Config, resourceHelper: ResourceHelper, rxBus: RxBusWrapper, hardLimits: HardLimits): Profile.ValidityCheck {
val notify = true val notify = true
var valid = true val validityCheck = Profile.ValidityCheck()
val description = pump.pumpDescription val description = pump.pumpDescription
if (!description.is30minBasalRatesCapable) { for (basal in basalBlocks) {
for (basal in basalBlocks) { val basalAmount = basal.amount * percentage / 100.0
if (!description.is30minBasalRatesCapable) {
// Check for hours alignment // Check for hours alignment
val duration: Long = basal.duration val duration: Long = basal.duration
if (duration % 3600000 != 0L) { if (duration % 3600000 != 0L) {
if (notify && config.APS) { if (notify && config.APS) {
val notification = Notification(Notification.BASAL_PROFILE_NOT_ALIGNED_TO_HOURS, resourceHelper.gs(R.string.basalprofilenotaligned, from), Notification.NORMAL) val notification = Notification(
Notification.BASAL_PROFILE_NOT_ALIGNED_TO_HOURS,
resourceHelper.gs(R.string.basalprofilenotaligned, from),
Notification.NORMAL
)
rxBus.send(EventNewNotification(notification)) rxBus.send(EventNewNotification(notification))
} }
valid = false validityCheck.isValid = false
} validityCheck.reasons.add(
// Check for minimal basal value resourceHelper.gs(
if (basal.amount < description.basalMinimumRate) { R.string.basalprofilenotaligned,
basal.amount = description.basalMinimumRate from
if (notify) sendBelowMinimumNotification(from, rxBus, resourceHelper) )
valid = false )
} else if (basal.amount > description.basalMaximumRate) { break
basal.amount = description.basalMaximumRate
if (notify) sendAboveMaximumNotification(from, rxBus, resourceHelper)
valid = false
} }
} }
// Check for minimal basal value
if (basalAmount < description.basalMinimumRate) {
basal.amount = description.basalMinimumRate
if (notify) sendBelowMinimumNotification(from, rxBus, resourceHelper)
validityCheck.isValid = false
validityCheck.reasons.add(resourceHelper.gs(R.string.minimalbasalvaluereplaced, from))
break
} else if (basalAmount > description.basalMaximumRate) {
basal.amount = description.basalMaximumRate
if (notify) sendAboveMaximumNotification(from, rxBus, resourceHelper)
validityCheck.isValid = false
validityCheck.reasons.add(resourceHelper.gs(R.string.maximumbasalvaluereplaced, from))
break
}
if (!hardLimits.isInRange(basalAmount, 0.01, hardLimits.maxBasal())) {
validityCheck.isValid = false
validityCheck.reasons.add(resourceHelper.gs(R.string.value_out_of_hard_limits, resourceHelper.gs(R.string.basal_value), basalAmount))
break
}
} }
return valid if (!hardLimits.isInRange(dia, hardLimits.minDia(), hardLimits.maxDia())) {
validityCheck.isValid = false
validityCheck.reasons.add(resourceHelper.gs(R.string.value_out_of_hard_limits, resourceHelper.gs(R.string.profile_dia), dia))
}
for (ic in icBlocks)
if (!hardLimits.isInRange(ic.amount * 100.0 / percentage, hardLimits.minIC(), hardLimits.maxIC())) {
validityCheck.isValid = false
validityCheck.reasons.add(resourceHelper.gs(R.string.value_out_of_hard_limits, resourceHelper.gs(R.string.profile_carbs_ratio_value), ic.amount * 100.0 / percentage))
break
}
for (isf in isfBlocks)
if (!hardLimits.isInRange(toMgdl(isf.amount * 100.0 / percentage, units), HardLimits.MIN_ISF, HardLimits.MAX_ISF)) {
validityCheck.isValid = false
validityCheck.reasons.add(resourceHelper.gs(R.string.value_out_of_hard_limits, resourceHelper.gs(R.string.profile_sensitivity_value), isf.amount * 100.0 / percentage))
break
}
for (target in targetBlocks) {
if (hardLimits.isInRange(
Round.roundTo(target.lowTarget, 0.1),
HardLimits.VERY_HARD_LIMIT_MIN_BG[0].toDouble(),
HardLimits.VERY_HARD_LIMIT_MIN_BG[1].toDouble()
)
) {
validityCheck.isValid = false
validityCheck.reasons.add(resourceHelper.gs(R.string.value_out_of_hard_limits, resourceHelper.gs(R.string.profile_low_target), target.lowTarget))
break
}
if (hardLimits.isInRange(
Round.roundTo(target.highTarget, 0.1),
HardLimits.VERY_HARD_LIMIT_MAX_BG[0].toDouble(),
HardLimits.VERY_HARD_LIMIT_MAX_BG[1].toDouble()
)
) {
validityCheck.isValid = false
validityCheck.reasons.add(resourceHelper.gs(R.string.value_out_of_hard_limits, resourceHelper.gs(R.string.profile_high_target), target.highTarget))
break
}
}
return validityCheck
} }
protected open fun sendBelowMinimumNotification(from: String, rxBus: RxBusWrapper, resourceHelper: ResourceHelper) { protected open fun sendBelowMinimumNotification(from: String, rxBus: RxBusWrapper, resourceHelper: ResourceHelper) {

View file

@ -17,15 +17,18 @@ import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.extensions.getCustomizedName import info.nightscout.androidaps.extensions.getCustomizedName
import info.nightscout.androidaps.extensions.pureProfileFromJson import info.nightscout.androidaps.extensions.pureProfileFromJson
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.HtmlHelper import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import org.json.JSONObject import org.json.JSONObject
import java.io.File.separator
import java.text.DecimalFormat import java.text.DecimalFormat
import javax.inject.Inject import javax.inject.Inject
@ -39,6 +42,7 @@ class ProfileViewerDialog : DaggerDialogFragment() {
@Inject lateinit var activePlugin: ActivePlugin @Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var config: Config @Inject lateinit var config: Config
@Inject lateinit var rxBus: RxBusWrapper @Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var hardLimits: HardLimits
private var time: Long = 0 private var time: Long = 0
@ -149,7 +153,9 @@ class ProfileViewerDialog : DaggerDialogFragment() {
} }
binding.noprofile.visibility = View.GONE binding.noprofile.visibility = View.GONE
binding.invalidprofile.visibility = if (profile1.isValid("ProfileViewDialog", activePlugin.activePump, config, resourceHelper, rxBus)) View.GONE else View.VISIBLE val validity = profile1.isValid("ProfileViewDialog", activePlugin.activePump, config, resourceHelper, rxBus, hardLimits)
binding.invalidprofile.text = resourceHelper.gs(R.string.invalidprofile) + "\n" + validity.reasons.joinToString(separator = "\n")
binding.invalidprofile.visibility = validity.isValid.not().toVisibility()
} }
else else
profile?.let { profile?.let {
@ -164,7 +170,9 @@ class ProfileViewerDialog : DaggerDialogFragment() {
binding.basalGraph.show(it) binding.basalGraph.show(it)
binding.noprofile.visibility = View.GONE binding.noprofile.visibility = View.GONE
binding.invalidprofile.visibility = if (it.isValid("ProfileViewDialog", activePlugin.activePump, config, resourceHelper, rxBus)) View.GONE else View.VISIBLE val validity = it.isValid("ProfileViewDialog", activePlugin.activePump, config, resourceHelper, rxBus, hardLimits)
binding.invalidprofile.text = resourceHelper.gs(R.string.invalidprofile) + "\n" + validity.reasons.joinToString(separator = "\n")
binding.invalidprofile.visibility = validity.isValid.not().toVisibility()
} }
} }

View file

@ -6,6 +6,7 @@ import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DecimalFormatter.to0Decimal import info.nightscout.androidaps.utils.DecimalFormatter.to0Decimal
import info.nightscout.androidaps.utils.DecimalFormatter.to1Decimal import info.nightscout.androidaps.utils.DecimalFormatter.to1Decimal
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.Round import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import org.joda.time.DateTime import org.joda.time.DateTime
@ -13,7 +14,9 @@ import org.json.JSONObject
interface Profile { interface Profile {
fun isValid(from: String, pump: Pump, config: Config, resourceHelper: ResourceHelper, rxBus: RxBusWrapper): Boolean class ValidityCheck(var isValid: Boolean = true, val reasons: ArrayList<String> = arrayListOf())
fun isValid(from: String, pump: Pump, config: Config, resourceHelper: ResourceHelper, rxBus: RxBusWrapper, hardLimits: HardLimits): ValidityCheck
/** /**
* Units used for ISF & target * Units used for ISF & target

View file

@ -46,6 +46,18 @@ interface ProfileFunction {
*/ */
fun getRequestedProfile(): ProfileSwitch? fun getRequestedProfile(): ProfileSwitch?
/**
* Build a new circadian profile switch request based on provided profile
*
* @param profileStore ProfileStore to use
* @param profileName this profile from profile store
* @param durationInMinutes
* @param percentage 100 = no modification
* @param timeShiftInHours 0 = no modification
* @param timestamp expected time
*/
fun buildProfileSwitch(profileStore: ProfileStore, profileName: String, durationInMinutes: Int, percentage: Int, timeShiftInHours: Int, timestamp: Long): ProfileSwitch
/** /**
* Create a new circadian profile switch request based on provided profile * Create a new circadian profile switch request based on provided profile
* *

View file

@ -63,7 +63,7 @@ class PumpDescription() {
is30minBasalRatesCapable = false is30minBasalRatesCapable = false
isRefillingCapable = true isRefillingCapable = true
isBatteryReplaceable = true isBatteryReplaceable = true
storesCarbInfo = true storesCarbInfo = false
supportsTDDs = false supportsTDDs = false
needsManualTDDLoad = true needsManualTDDLoad = true
hasCustomUnreachableAlertCheck = false hasCustomUnreachableAlertCheck = false

View file

@ -1,7 +1,8 @@
package info.nightscout.androidaps.utils package info.nightscout.androidaps.utils
import android.content.Context import android.content.Context
import info.nightscout.androidaps.R import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.transactions.InsertTherapyEventAnnouncementTransaction import info.nightscout.androidaps.database.transactions.InsertTherapyEventAnnouncementTransaction
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
@ -15,6 +16,7 @@ import javax.inject.Singleton
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@OpenForTesting
@Singleton @Singleton
class HardLimits @Inject constructor( class HardLimits @Inject constructor(
private val aapsLogger: AAPSLogger, private val aapsLogger: AAPSLogger,
@ -81,9 +83,12 @@ class HardLimits @Inject constructor(
fun maxIC(): Double = MAX_IC[loadAge()] fun maxIC(): Double = MAX_IC[loadAge()]
// safety checks // safety checks
fun checkOnlyHardLimits(value: Double, valueName: Int, lowLimit: Double, highLimit: Double): Boolean = fun checkHardLimits(value: Double, valueName: Int, lowLimit: Double, highLimit: Double): Boolean =
value == verifyHardLimits(value, valueName, lowLimit, highLimit) value == verifyHardLimits(value, valueName, lowLimit, highLimit)
fun isInRange(value: Double, lowLimit: Double, highLimit: Double): Boolean =
value in lowLimit..highLimit
fun verifyHardLimits(value: Double, valueName: Int, lowLimit: Double, highLimit: Double): Double { fun verifyHardLimits(value: Double, valueName: Int, lowLimit: Double, highLimit: Double): Double {
var newValue = value var newValue = value
if (newValue < lowLimit || newValue > highLimit) { if (newValue < lowLimit || newValue > highLimit) {
@ -98,4 +103,4 @@ class HardLimits @Inject constructor(
} }
return newValue return newValue
} }
} }

View file

@ -90,7 +90,11 @@ class SPImplementation @Inject constructor(
return try { return try {
sharedPreferences.getLong(key, defaultValue) sharedPreferences.getLong(key, defaultValue)
} catch (e: Exception) { } catch (e: Exception) {
SafeParse.stringToLong(sharedPreferences.getString(key, defaultValue.toString())) try {
SafeParse.stringToLong(sharedPreferences.getString(key, defaultValue.toString()))
} catch (e: Exception) {
defaultValue
}
} }
} }

View file

@ -57,6 +57,12 @@
<string name="key_active_pump_change_timestamp" translatable="false">active_pump_change_timestamp</string> <string name="key_active_pump_change_timestamp" translatable="false">active_pump_change_timestamp</string>
<string name="key_active_pump_type" translatable="false">active_pump_type</string> <string name="key_active_pump_type" translatable="false">active_pump_type</string>
<string name="key_active_pump_serial_number" translatable="false">active_pump_serial_number</string> <string name="key_active_pump_serial_number" translatable="false">active_pump_serial_number</string>
<string name="key_age" translatable="false">age</string>
<string name="key_child" translatable="false">child</string>
<string name="key_teenage" translatable="false">teenage</string>
<string name="key_adult" translatable="false">adult</string>
<string name="key_resistantadult" translatable="false">resistantadult</string>
<string name="key_pregnant" translatable="false">pregnant</string>
<!-- General--> <!-- General-->
<string name="refresh">Refresh</string> <string name="refresh">Refresh</string>
@ -472,6 +478,22 @@
<string name="uel_loop_removed">LOOP REMOVED</string> <string name="uel_loop_removed">LOOP REMOVED</string>
<string name="uel_other">OTHER</string> <string name="uel_other">OTHER</string>
<!-- HardLimits -->
<string name="profile_low_target">Profile low target</string>
<string name="profile_high_target">Profile high target</string>
<string name="temp_target_low_target">Temporary target bottom value</string>
<string name="temp_target_high_target">Temporary target top value</string>
<string name="temp_target_value">Temporary target value</string>
<string name="profile_dia">Profile DIA value</string>
<string name="profile_sensitivity_value">Profile sensitivity value</string>
<string name="profile_max_daily_basal_value">Maximal profile basal value</string>
<string name="current_basal_value">Current basal value</string>
<string name="profile_carbs_ratio_value">Profile carbs ratio value</string>
<string name="valuelimitedto">%1$.2f limited to %2$.2f</string>
<string name="valueoutofrange">»%1$s« is out of hard limits</string>
<string name="value_out_of_hard_limits">»%1$s« %2$.2f is out of hard limits</string>
<string name="basal_value">Basal value</string>
<plurals name="days"> <plurals name="days">
<item quantity="one">%1$d day</item> <item quantity="one">%1$d day</item>
<item quantity="other">%1$d days</item> <item quantity="other">%1$d days</item>

View file

@ -2,11 +2,10 @@ package info.nightscout.androidaps.data
import android.content.Context import android.content.Context
import dagger.android.AndroidInjector import dagger.android.AndroidInjector
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.TestBase import info.nightscout.androidaps.TestBase
import info.nightscout.androidaps.TestPumpPlugin import info.nightscout.androidaps.TestPumpPlugin
import info.nightscout.androidaps.core.R import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.events.Event import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.extensions.pureProfileFromJson import info.nightscout.androidaps.extensions.pureProfileFromJson
import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.interfaces.Config
@ -14,18 +13,18 @@ import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.TestAapsSchedulers import info.nightscout.androidaps.utils.rx.TestAapsSchedulers
import info.nightscout.androidaps.utils.sharedPreferences.SP
import org.json.JSONObject import org.json.JSONObject
import org.junit.Assert import org.junit.Assert
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.`when` import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyString import org.mockito.Mockito.anyString
import org.mockito.Mockito.doNothing
import java.util.* import java.util.*
/** /**
@ -38,25 +37,29 @@ class ProfileTest : TestBase() {
@Mock lateinit var resourceHelper: ResourceHelper @Mock lateinit var resourceHelper: ResourceHelper
@Mock lateinit var context: Context @Mock lateinit var context: Context
@Mock lateinit var config: Config @Mock lateinit var config: Config
@Mock lateinit var sp: SP
@Mock lateinit var repository: AppRepository
private lateinit var rxBus: RxBusWrapper private lateinit var rxBus: RxBusWrapper
private lateinit var dateUtil: DateUtil private lateinit var dateUtil: DateUtil
private lateinit var testPumpPlugin: TestPumpPlugin private lateinit var testPumpPlugin: TestPumpPlugin
private lateinit var hardLimits: HardLimits
private var okProfile = "{\"dia\":\"3\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"sens\":[{\"time\":\"00:00\",\"value\":\"100\"},{\"time\":\"2:00\",\"value\":\"110\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"0.1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"5\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}" private var okProfile = "{\"dia\":\"5\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"sens\":[{\"time\":\"00:00\",\"value\":\"6\"},{\"time\":\"2:00\",\"value\":\"6.2\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"0.1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"5\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}"
private var belowLimitValidProfile = "{\"dia\":\"3\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"100\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"0.001\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"5\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}" private var belowLimitValidProfile = "{\"dia\":\"5\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"100\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"0.001\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"5\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}"
private var notAlignedBasalValidProfile = "{\"dia\":\"3\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"100\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:30\",\"value\":\"0.1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"5\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}" private var notAlignedBasalValidProfile = "{\"dia\":\"5\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"100\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:30\",\"value\":\"0.1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"5\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}"
private var notStartingAtZeroValidProfile = "{\"dia\":\"3\",\"carbratio\":[{\"time\":\"00:30\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"100\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"0.1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"5\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}" private var notStartingAtZeroValidProfile = "{\"dia\":\"5\",\"carbratio\":[{\"time\":\"00:30\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"100\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"0.1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"5\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}"
private var noUnitsValidProfile = "{\"dia\":\"3\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"100\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"0.1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"5\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\"}" private var noUnitsValidProfile = "{\"dia\":\"5\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"100\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"0.1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"5\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\"}"
private var wrongProfile = "{\"dia\":\"3\",\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"100\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"0.1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"5\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}" private var wrongProfile = "{\"dia\":\"5\",\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"100\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"0.1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"5\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}"
//String profileStore = "{\"defaultProfile\":\"Default\",\"store\":{\"Default\":" + validProfile + "}}"; //String profileStore = "{\"defaultProfile\":\"Default\",\"store\":{\"Default\":" + validProfile + "}}";
@Before @Before
fun prepare() { fun prepare() {
testPumpPlugin = TestPumpPlugin(HasAndroidInjector { AndroidInjector { } }) testPumpPlugin = TestPumpPlugin { AndroidInjector { } }
dateUtil = DateUtil(context) dateUtil = DateUtil(context)
rxBus = RxBusWrapper(TestAapsSchedulers()) rxBus = RxBusWrapper(TestAapsSchedulers())
hardLimits = HardLimits(aapsLogger, rxBus, sp, resourceHelper, context, repository)
`when`(activePluginProvider.activePump).thenReturn(testPumpPlugin) `when`(activePluginProvider.activePump).thenReturn(testPumpPlugin)
`when`(resourceHelper.gs(R.string.profile_per_unit)).thenReturn("/U") `when`(resourceHelper.gs(R.string.profile_per_unit)).thenReturn("/U")
`when`(resourceHelper.gs(R.string.profile_carbs_per_unit)).thenReturn("g/U") `when`(resourceHelper.gs(R.string.profile_carbs_per_unit)).thenReturn("g/U")
@ -69,10 +72,10 @@ class ProfileTest : TestBase() {
// Test valid profile // Test valid profile
var p = ProfileSealed.Pure(pureProfileFromJson(JSONObject(okProfile), dateUtil)!!) var p = ProfileSealed.Pure(pureProfileFromJson(JSONObject(okProfile), dateUtil)!!)
Assert.assertEquals(true, p.isValid("Test", testPumpPlugin, config, resourceHelper, rxBus)) Assert.assertEquals(true, p.isValid("Test", testPumpPlugin, config, resourceHelper, rxBus, hardLimits).isValid)
// Assert.assertEquals(true, p.log().contains("NS units: mmol")) // Assert.assertEquals(true, p.log().contains("NS units: mmol"))
// JSONAssert.assertEquals(JSONObject(okProfile), p.toPureNsJson(dateUtil), false) // JSONAssert.assertEquals(JSONObject(okProfile), p.toPureNsJson(dateUtil), false)
Assert.assertEquals(3.0, p.dia, 0.01) Assert.assertEquals(5.0, p.dia, 0.01)
// Assert.assertEquals(TimeZone.getTimeZone("UTC"), p.timeZone) // Assert.assertEquals(TimeZone.getTimeZone("UTC"), p.timeZone)
Assert.assertEquals("00:30", dateUtil.format_HH_MM(30 * 60)) Assert.assertEquals("00:30", dateUtil.format_HH_MM(30 * 60))
val c = Calendar.getInstance() val c = Calendar.getInstance()
@ -80,13 +83,13 @@ class ProfileTest : TestBase() {
c[Calendar.MINUTE] = 0 c[Calendar.MINUTE] = 0
c[Calendar.SECOND] = 0 c[Calendar.SECOND] = 0
c[Calendar.MILLISECOND] = 0 c[Calendar.MILLISECOND] = 0
Assert.assertEquals(1800.0, p.getIsfMgdl(c.timeInMillis), 0.01) Assert.assertEquals(108.0, p.getIsfMgdl(c.timeInMillis), 0.01)
c[Calendar.HOUR_OF_DAY] = 2 c[Calendar.HOUR_OF_DAY] = 2
Assert.assertEquals(1980.0, p.getIsfMgdl(c.timeInMillis), 0.01) Assert.assertEquals(111.6, p.getIsfMgdl(c.timeInMillis), 0.01)
// Assert.assertEquals(110.0, p.getIsfTimeFromMidnight(2 * 60 * 60), 0.01) // Assert.assertEquals(110.0, p.getIsfTimeFromMidnight(2 * 60 * 60), 0.01)
Assert.assertEquals(""" Assert.assertEquals("""
00:00 100,0 mmol/U 00:00 6,0 mmol/U
02:00 110,0 mmol/U 02:00 6,2 mmol/U
""".trimIndent(), p.getIsfList(resourceHelper, dateUtil).replace(".", ",")) """.trimIndent(), p.getIsfList(resourceHelper, dateUtil).replace(".", ","))
Assert.assertEquals(30.0, p.getIc(c.timeInMillis), 0.01) Assert.assertEquals(30.0, p.getIc(c.timeInMillis), 0.01)
Assert.assertEquals(30.0, p.getIcTimeFromMidnight(2 * 60 * 60), 0.01) Assert.assertEquals(30.0, p.getIcTimeFromMidnight(2 * 60 * 60), 0.01)
@ -121,7 +124,7 @@ class ProfileTest : TestBase() {
//Test basal profile below limit //Test basal profile below limit
p = ProfileSealed.Pure(pureProfileFromJson(JSONObject(belowLimitValidProfile), dateUtil)!!) p = ProfileSealed.Pure(pureProfileFromJson(JSONObject(belowLimitValidProfile), dateUtil)!!)
p.isValid("Test", testPumpPlugin, config, resourceHelper, rxBus) p.isValid("Test", testPumpPlugin, config, resourceHelper, rxBus, hardLimits)
// Test profile w/o units // Test profile w/o units
Assert.assertNull(pureProfileFromJson(JSONObject(noUnitsValidProfile), dateUtil)) Assert.assertNull(pureProfileFromJson(JSONObject(noUnitsValidProfile), dateUtil))
@ -139,21 +142,21 @@ class ProfileTest : TestBase() {
Assert.assertEquals(0.05, p.getBasal(c.timeInMillis), 0.01) Assert.assertEquals(0.05, p.getBasal(c.timeInMillis), 0.01)
Assert.assertEquals(1.2, p.percentageBasalSum(), 0.01) Assert.assertEquals(1.2, p.percentageBasalSum(), 0.01)
Assert.assertEquals(60.0, p.getIc(c.timeInMillis), 0.01) Assert.assertEquals(60.0, p.getIc(c.timeInMillis), 0.01)
Assert.assertEquals(3960.0, p.getIsfMgdl(c.timeInMillis), 0.01) Assert.assertEquals(223.2, p.getIsfMgdl(c.timeInMillis), 0.01)
// Test timeshift functionality // Test timeshift functionality
p = ProfileSealed.Pure(pureProfileFromJson(JSONObject(okProfile), dateUtil)!!) p = ProfileSealed.Pure(pureProfileFromJson(JSONObject(okProfile), dateUtil)!!)
p.ts = 1 p.ts = 1
Assert.assertEquals( Assert.assertEquals(
""" """
00:00 110.0 mmol/U 00:00 6.2 mmol/U
01:00 100.0 mmol/U 01:00 6.0 mmol/U
03:00 110.0 mmol/U 03:00 6.2 mmol/U
""".trimIndent(), p.getIsfList(resourceHelper, dateUtil)) """.trimIndent(), p.getIsfList(resourceHelper, dateUtil))
// Test hour alignment // Test hour alignment
testPumpPlugin.pumpDescription.is30minBasalRatesCapable = false testPumpPlugin.pumpDescription.is30minBasalRatesCapable = false
p = ProfileSealed.Pure(pureProfileFromJson(JSONObject(notAlignedBasalValidProfile), dateUtil)!!) p = ProfileSealed.Pure(pureProfileFromJson(JSONObject(notAlignedBasalValidProfile), dateUtil)!!)
p.isValid("Test", testPumpPlugin, config, resourceHelper, rxBus) p.isValid("Test", testPumpPlugin, config, resourceHelper, rxBus, hardLimits)
} }
} }