Merge branch 'upstream-dev' into fix-history-id

This commit is contained in:
Andrei Vereha 2022-05-10 19:15:44 +02:00
commit 770bb93e6b
66 changed files with 4254 additions and 49 deletions

View file

@ -28,6 +28,7 @@ import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.PluginStore
import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
import info.nightscout.androidaps.plugins.general.autotune.AutotunePlugin
import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin
import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin
import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus
@ -65,6 +66,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
@Inject lateinit var config: Config
@Inject lateinit var automationPlugin: AutomationPlugin
@Inject lateinit var autotunePlugin: AutotunePlugin
@Inject lateinit var danaRPlugin: DanaRPlugin
@Inject lateinit var danaRKoreanPlugin: DanaRKoreanPlugin
@Inject lateinit var danaRv2Plugin: DanaRv2Plugin
@ -187,6 +189,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
addPreferencesFromResourceIfEnabled(tidepoolPlugin, rootKey)
addPreferencesFromResourceIfEnabled(smsCommunicatorPlugin, rootKey)
addPreferencesFromResourceIfEnabled(automationPlugin, rootKey)
addPreferencesFromResourceIfEnabled(autotunePlugin, rootKey)
addPreferencesFromResourceIfEnabled(wearPlugin, rootKey)
addPreferencesFromResourceIfEnabled(statusLinePlugin, rootKey)
addPreferencesFromResource(R.xml.pref_alerts, rootKey)

View file

@ -12,6 +12,7 @@ import info.nightscout.androidaps.dana.di.DanaModule
import info.nightscout.androidaps.danar.di.DanaRModule
import info.nightscout.androidaps.danars.di.DanaRSModule
import info.nightscout.androidaps.database.DatabaseModule
import info.nightscout.androidaps.dependencyInjection.AutotuneModule
import info.nightscout.androidaps.diaconn.di.DiaconnG8Module
import info.nightscout.androidaps.insight.di.InsightDatabaseModule
import info.nightscout.androidaps.insight.di.InsightModule
@ -37,6 +38,7 @@ import javax.inject.Singleton
ReceiversModule::class,
ServicesModule::class,
AutomationModule::class,
AutotuneModule::class,
CommandQueueModule::class,
ObjectivesModule::class,
WizardModule::class,

View file

@ -15,6 +15,7 @@ import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
import info.nightscout.androidaps.plugins.configBuilder.PluginStore
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctionImplementation
import info.nightscout.androidaps.plugins.general.autotune.AutotunePlugin
import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefsImpl
import info.nightscout.androidaps.plugins.general.maintenance.PrefFileListProvider
import info.nightscout.androidaps.plugins.general.nsclient.DataSyncSelectorImplementation
@ -98,6 +99,7 @@ open class AppModule {
@Binds fun bindImportExportPrefsInterface(importExportPrefs: ImportExportPrefsImpl): ImportExportPrefs
@Binds fun bindIconsProviderInterface(iconsProvider: IconsProviderImplementation): IconsProvider
@Binds fun bindLoopInterface(loopPlugin: LoopPlugin): Loop
@Binds fun bindAutotuneInterface(autotunePlugin: AutotunePlugin): Autotune
@Binds fun bindIobCobCalculatorInterface(iobCobCalculatorPlugin: IobCobCalculatorPlugin): IobCobCalculator
@Binds fun bindSmsCommunicatorInterface(smsCommunicatorPlugin: SmsCommunicatorPlugin): SmsCommunicator
@Binds fun bindDataSyncSelector(dataSyncSelectorImplementation: DataSyncSelectorImplementation): DataSyncSelector

View file

@ -0,0 +1,23 @@
package info.nightscout.androidaps.dependencyInjection
import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.plugins.general.autotune.AutotuneCore
import info.nightscout.androidaps.plugins.general.autotune.AutotuneIob
import info.nightscout.androidaps.plugins.general.autotune.AutotunePrep
import info.nightscout.androidaps.plugins.general.autotune.AutotuneFS
import info.nightscout.androidaps.plugins.general.autotune.data.*
@Module
@Suppress("unused")
abstract class AutotuneModule {
@ContributesAndroidInjector abstract fun autoTunePrepInjector(): AutotunePrep
@ContributesAndroidInjector abstract fun autoTuneIobInjector(): AutotuneIob
@ContributesAndroidInjector abstract fun autoTuneCoreInjector(): AutotuneCore
@ContributesAndroidInjector abstract fun autoTuneFSInjector(): AutotuneFS
@ContributesAndroidInjector abstract fun autoTuneATProfileInjector(): ATProfile
@ContributesAndroidInjector abstract fun autoTuneBGDatumInjector(): BGDatum
@ContributesAndroidInjector abstract fun autoTuneCRDatumInjector(): CRDatum
@ContributesAndroidInjector abstract fun autoTunePreppedGlucoseInjector(): PreppedGlucose
}

View file

@ -12,6 +12,7 @@ import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesFragm
import info.nightscout.androidaps.plugins.constraints.objectives.activities.ObjectivesExamDialog
import info.nightscout.androidaps.plugins.general.actions.ActionsFragment
import info.nightscout.androidaps.plugins.general.automation.AutomationFragment
import info.nightscout.androidaps.plugins.general.autotune.AutotuneFragment
import info.nightscout.androidaps.plugins.general.food.FoodFragment
import info.nightscout.androidaps.plugins.general.maintenance.MaintenanceFragment
import info.nightscout.androidaps.plugins.general.nsclient.NSClientFragment
@ -36,6 +37,7 @@ abstract class FragmentsModule {
@ContributesAndroidInjector abstract fun contributesActionsFragment(): ActionsFragment
@ContributesAndroidInjector abstract fun contributesAutomationFragment(): AutomationFragment
@ContributesAndroidInjector abstract fun contributesAutotuneFragment(): AutotuneFragment
@ContributesAndroidInjector abstract fun contributesBGSourceFragment(): BGSourceFragment
@ContributesAndroidInjector abstract fun contributesConfigBuilderFragment(): ConfigBuilderFragment

View file

@ -25,6 +25,7 @@ import info.nightscout.androidaps.plugins.constraints.storage.StorageConstraintP
import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerPlugin
import info.nightscout.androidaps.plugins.general.actions.ActionsPlugin
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
import info.nightscout.androidaps.plugins.general.autotune.AutotunePlugin
import info.nightscout.androidaps.plugins.general.dataBroadcaster.DataBroadcastPlugin
import info.nightscout.androidaps.plugins.general.food.FoodPlugin
import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin
@ -232,6 +233,12 @@ abstract class PluginsModule {
@IntKey(250)
abstract fun bindAutomationPlugin(plugin: AutomationPlugin): PluginBase
@Binds
@AllConfigs
@IntoMap
@IntKey(255)
abstract fun bindAutotunePlugin(plugin: AutotunePlugin): PluginBase
@Binds
@AllConfigs
@IntoMap

View file

@ -0,0 +1,516 @@
package info.nightscout.androidaps.plugins.general.autotune
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.LocalInsulin
import info.nightscout.androidaps.plugins.general.autotune.data.ATProfile
import info.nightscout.androidaps.plugins.general.autotune.data.PreppedGlucose
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
import info.nightscout.androidaps.utils.Round
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AutotuneCore @Inject constructor(
private val sp: SP,
private val autotuneFS: AutotuneFS
) {
fun tuneAllTheThings(preppedGlucose: PreppedGlucose, previousAutotune: ATProfile, pumpProfile: ATProfile): ATProfile {
//var pumpBasalProfile = pumpProfile.basalprofile;
val pumpBasalProfile = pumpProfile.basal
//console.error(pumpBasalProfile);
var basalProfile = previousAutotune.basal
//console.error(basalProfile);
//console.error(isfProfile);
var isf = previousAutotune.isf
//console.error(isf);
var carbRatio = previousAutotune.ic
//console.error(carbRatio);
var csf = isf / carbRatio
var dia = previousAutotune.dia
var peak = previousAutotune.peak
val csfGlucose = preppedGlucose.csfGlucoseData
val isfGlucose = preppedGlucose.isfGlucoseData
val basalGlucose = preppedGlucose.basalGlucoseData
val crData = preppedGlucose.crData
val diaDeviations = preppedGlucose.diaDeviations
val peakDeviations = preppedGlucose.peakDeviations
val pumpISF = pumpProfile.isf
val pumpCarbRatio = pumpProfile.ic
val pumpCSF = pumpISF / pumpCarbRatio
// Autosens constraints
val autotuneMax = sp.getDouble(R.string.key_openapsama_autosens_max, 1.2)
val autotuneMin = sp.getDouble(R.string.key_openapsama_autosens_min, 0.7)
val min5minCarbImpact = sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, 3.0)
// tune DIA
var newDia = dia
if (diaDeviations.size > 0)
{
val currentDiaMeanDev = diaDeviations[2].meanDeviation
val currentDiaRMSDev = diaDeviations[2].rmsDeviation
//Console.WriteLine(DIA,currentDIAMeanDev,currentDIARMSDev);
var minMeanDeviations = 1000000.0
var minRmsDeviations = 1000000.0
var meanBest = 2
var rmsBest = 2
for (i in 0..diaDeviations.size-1)
{
val meanDeviations = diaDeviations[i].meanDeviation
val rmsDeviations = diaDeviations[i].rmsDeviation
if (meanDeviations < minMeanDeviations)
{
minMeanDeviations = Round.roundTo(meanDeviations, 0.001)
meanBest = i
}
if (rmsDeviations < minRmsDeviations)
{
minRmsDeviations = Round.roundTo(rmsDeviations, 0.001)
rmsBest = i
}
}
log("Best insulinEndTime for meanDeviations: ${diaDeviations[meanBest].dia} hours")
log("Best insulinEndTime for RMSDeviations: ${diaDeviations[rmsBest].dia} hours")
if (meanBest < 2 && rmsBest < 2)
{
if (diaDeviations[1].meanDeviation < currentDiaMeanDev * 0.99 && diaDeviations[1].rmsDeviation < currentDiaRMSDev * 0.99)
newDia = diaDeviations[1].dia
}
else if (meanBest > 2 && rmsBest > 2)
{
if (diaDeviations[3].meanDeviation < currentDiaMeanDev * 0.99 && diaDeviations[3].rmsDeviation < currentDiaRMSDev * 0.99)
newDia = diaDeviations[3].dia
}
if (newDia > 12.0)
{
log("insulinEndTime maximum is 12h: not raising further")
newDia = 12.0
}
if (newDia != dia)
log("Adjusting insulinEndTime from $dia to $newDia hours")
else
log("Leaving insulinEndTime unchanged at $dia hours")
}
// tune insulinPeakTime
var newPeak = peak
if (peakDeviations.size > 2)
{
val currentPeakMeanDev = peakDeviations[2].meanDeviation
val currentPeakRMSDev = peakDeviations[2].rmsDeviation
//Console.WriteLine(currentPeakMeanDev);
var minMeanDeviations = 1000000.0
var minRmsDeviations = 1000000.0
var meanBest = 2
var rmsBest = 2
for (i in 0..peakDeviations.size-1)
{
val meanDeviations = peakDeviations[i].meanDeviation;
val rmsDeviations = peakDeviations[i].rmsDeviation;
if (meanDeviations < minMeanDeviations)
{
minMeanDeviations = Round.roundTo(meanDeviations, 0.001)
meanBest = i
}
if (rmsDeviations < minRmsDeviations)
{
minRmsDeviations = Round.roundTo(rmsDeviations, 0.001)
rmsBest = i
}
}
log("Best insulinPeakTime for meanDeviations: ${peakDeviations[meanBest].peak} minutes")
log("Best insulinPeakTime for RMSDeviations: ${peakDeviations[rmsBest].peak} minutes")
if (meanBest < 2 && rmsBest < 2)
{
if (peakDeviations[1].meanDeviation < currentPeakMeanDev * 0.99 && peakDeviations[1].rmsDeviation < currentPeakRMSDev * 0.99)
newPeak = peakDeviations[1].peak
}
else if (meanBest > 2 && rmsBest > 2)
{
if (peakDeviations[3].meanDeviation < currentPeakMeanDev * 0.99 && peakDeviations[3].rmsDeviation < currentPeakRMSDev * 0.99)
newPeak = peakDeviations[3].peak
}
if (newPeak != peak)
log("Adjusting insulinPeakTime from " + peak + " to " + newPeak + " minutes")
else
log("Leaving insulinPeakTime unchanged at " + peak)
}
// Calculate carb ratio (CR) independently of csf and isf
// Use the time period from meal bolus/carbs until COB is zero and IOB is < currentBasal/2
// For now, if another meal IOB/COB stacks on top of it, consider them together
// Compare beginning and ending BGs, and calculate how much more/less insulin is needed to neutralize
// Use entered carbs vs. starting IOB + delivered insulin + needed-at-end insulin to directly calculate CR.
//autotune-core (lib/autotune/index.js) #149-#165
var crTotalCarbs = 0.0
var crTotalInsulin = 0.0
for (i in crData.indices) {
val crDatum = crData[i]
val crBGChange = crDatum.crEndBG - crDatum.crInitialBG
val crInsulinReq = crBGChange / isf
//val crIOBChange = crDatum.crEndIOB - crDatum.crInitialIOB
crDatum.crInsulinTotal = crDatum.crInitialIOB + crDatum.crInsulin + crInsulinReq
//log(crDatum.crInitialIOB + " " + crDatum.crInsulin + " " + crInsulinReq + " " + crDatum.crInsulinTotal);
//val cr = Round.roundTo(crDatum.crCarbs / crDatum.crInsulinTotal, 0.001)
//log(crBGChange + " " + crInsulinReq + " " + crIOBChange + " " + crDatum.crInsulinTotal);
//log("CRCarbs: " + crDatum.crCarbs + " CRInsulin: " + crDatum.crInsulinTotal + " CR:" + cr);
if (crDatum.crInsulinTotal > 0) {
crTotalCarbs += crDatum.crCarbs
crTotalInsulin += crDatum.crInsulinTotal
}
}
//autotune-core (lib/autotune/index.js) #166-#169
crTotalInsulin = Round.roundTo(crTotalInsulin, 0.001)
var totalCR = 0.0
if (crTotalInsulin != 0.0)
totalCR = Round.roundTo(crTotalCarbs / crTotalInsulin, 0.001)
log("crTotalCarbs: $crTotalCarbs crTotalInsulin: $crTotalInsulin totalCR: $totalCR")
//autotune-core (lib/autotune/index.js) #170-#209 (already hourly in aaps)
// convert the basal profile to hourly if it isn't already
val hourlyBasalProfile = basalProfile
//log(hourlyPumpProfile.toString());
//log(hourlyBasalProfile.toString());
val newHourlyBasalProfile = DoubleArray(24)
for (i in 0..23) {
newHourlyBasalProfile[i] = hourlyBasalProfile[i]
}
val basalUntuned = previousAutotune.basalUntuned
//autotune-core (lib/autotune/index.js) #210-#266
// look at net deviations for each hour
for (hour in 0..23) {
var deviations = 0.0
for (i in basalGlucose.indices) {
val BGTime = Calendar.getInstance()
//var BGTime: Date? = null
if (basalGlucose[i].date != 0L) {
BGTime.setTimeInMillis(basalGlucose[i].date)
//BGTime = Date(basalGlucose[i].date)
} else {
log("Could not determine last BG time")
}
val myHour = BGTime.get(Calendar.HOUR_OF_DAY)
//val myHour = BGTime!!.hours
if (hour == myHour) {
//log.debug(basalGlucose[i].deviation);
deviations += basalGlucose[i].deviation
}
}
deviations = Round.roundTo(deviations, 0.001)
log("Hour $hour total deviations: $deviations mg/dL")
// calculate how much less or additional basal insulin would have been required to eliminate the deviations
// only apply 20% of the needed adjustment to keep things relatively stable
var basalNeeded = 0.2 * deviations / isf
basalNeeded = Round.roundTo(basalNeeded, 0.01)
// if basalNeeded is positive, adjust each of the 1-3 hour prior basals by 10% of the needed adjustment
log("Hour $hour basal adjustment needed: $basalNeeded U/hr")
if (basalNeeded > 0) {
for (offset in -3..-1) {
var offsetHour = hour + offset
if (offsetHour < 0) {
offsetHour += 24
}
//log.debug(offsetHour);
newHourlyBasalProfile[offsetHour] = newHourlyBasalProfile[offsetHour] + basalNeeded / 3
newHourlyBasalProfile[offsetHour] = Round.roundTo(newHourlyBasalProfile[offsetHour], 0.001)
}
// otherwise, figure out the percentage reduction required to the 1-3 hour prior basals
// and adjust all of them downward proportionally
} else if (basalNeeded < 0) {
var threeHourBasal = 0.0
for (offset in -3..-1) {
var offsetHour = hour + offset
if (offsetHour < 0) {
offsetHour += 24
}
threeHourBasal += newHourlyBasalProfile[offsetHour]
}
val adjustmentRatio = 1.0 + basalNeeded / threeHourBasal
//log.debug(adjustmentRatio);
for (offset in -3..-1) {
var offsetHour = hour + offset
if (offsetHour < 0) {
offsetHour += 24
}
newHourlyBasalProfile[offsetHour] = newHourlyBasalProfile[offsetHour] * adjustmentRatio
newHourlyBasalProfile[offsetHour] = Round.roundTo(newHourlyBasalProfile[offsetHour], 0.001)
}
}
}
//autotune-core (lib/autotune/index.js) #267-#294
for (hour in 0..23) {
//log.debug(newHourlyBasalProfile[hour],hourlyPumpProfile[hour].rate*1.2);
// cap adjustments at autosens_max and autosens_min
val maxRate = pumpBasalProfile[hour] * autotuneMax
val minRate = pumpBasalProfile[hour] * autotuneMin
if (newHourlyBasalProfile[hour] > maxRate) {
log("Limiting hour " + hour + " basal to " + Round.roundTo(maxRate, 0.01) + " (which is " + Round.roundTo(autotuneMax, 0.01) + " * pump basal of " + pumpBasalProfile[hour] + ")")
//log.debug("Limiting hour",hour,"basal to",maxRate.toFixed(2),"(which is 20% above pump basal of",hourlyPumpProfile[hour].rate,")");
newHourlyBasalProfile[hour] = maxRate
} else if (newHourlyBasalProfile[hour] < minRate) {
log("Limiting hour " + hour + " basal to " + Round.roundTo(minRate, 0.01) + " (which is " + autotuneMin + " * pump basal of " + newHourlyBasalProfile[hour] + ")")
//log.debug("Limiting hour",hour,"basal to",minRate.toFixed(2),"(which is 20% below pump basal of",hourlyPumpProfile[hour].rate,")");
newHourlyBasalProfile[hour] = minRate
}
newHourlyBasalProfile[hour] = Round.roundTo(newHourlyBasalProfile[hour], 0.001)
}
// some hours of the day rarely have data to tune basals due to meals.
// when no adjustments are needed to a particular hour, we should adjust it toward the average of the
// periods before and after it that do have data to be tuned
var lastAdjustedHour = 0
// scan through newHourlyBasalProfile and find hours where the rate is unchanged
//autotune-core (lib/autotune/index.js) #302-#323
for (hour in 0..23) {
if (hourlyBasalProfile[hour] == newHourlyBasalProfile[hour]) {
var nextAdjustedHour = 23
for (nextHour in hour..23) {
if (hourlyBasalProfile[nextHour] != newHourlyBasalProfile[nextHour]) {
nextAdjustedHour = nextHour
break
//} else {
// log("At hour: "+nextHour +" " + hourlyBasalProfile[nextHour] + " " +newHourlyBasalProfile[nextHour]);
}
}
//log.debug(hour, newHourlyBasalProfile);
newHourlyBasalProfile[hour] = Round.roundTo(0.8 * hourlyBasalProfile[hour] + 0.1 * newHourlyBasalProfile[lastAdjustedHour] + 0.1 * newHourlyBasalProfile[nextAdjustedHour], 0.001)
basalUntuned[hour]++
log("Adjusting hour " + hour + " basal from " + hourlyBasalProfile[hour] + " to " + newHourlyBasalProfile[hour] + " based on hour " + lastAdjustedHour + " = " + newHourlyBasalProfile[lastAdjustedHour] + " and hour " + nextAdjustedHour + " = " + newHourlyBasalProfile[nextAdjustedHour])
} else {
lastAdjustedHour = hour
}
}
//log(newHourlyBasalProfile.toString());
basalProfile = newHourlyBasalProfile
// Calculate carb ratio (CR) independently of csf and isf
// Use the time period from meal bolus/carbs until COB is zero and IOB is < currentBasal/2
// For now, if another meal IOB/COB stacks on top of it, consider them together
// Compare beginning and ending BGs, and calculate how much more/less insulin is needed to neutralize
// Use entered carbs vs. starting IOB + delivered insulin + needed-at-end insulin to directly calculate CR.
// calculate net deviations while carbs are absorbing
// measured from carb entry until COB and deviations both drop to zero
var deviations = 0.0
var mealCarbs = 0
var totalMealCarbs = 0
var totalDeviations = 0.0
val fullNewCSF: Double
//log.debug(CSFGlucose[0].mealAbsorption);
//log.debug(CSFGlucose[0]);
//autotune-core (lib/autotune/index.js) #346-#365
for (i in csfGlucose.indices) {
//log.debug(CSFGlucose[i].mealAbsorption, i);
if (csfGlucose[i].mealAbsorption === "start") {
deviations = 0.0
mealCarbs = csfGlucose[i].mealCarbs
} else if (csfGlucose[i].mealAbsorption === "end") {
deviations += csfGlucose[i].deviation
// compare the sum of deviations from start to end vs. current csf * mealCarbs
//log.debug(csf,mealCarbs);
//val csfRise = csf * mealCarbs
//log.debug(deviations,isf);
//log.debug("csfRise:",csfRise,"deviations:",deviations);
totalMealCarbs += mealCarbs
totalDeviations += deviations
} else {
//todo Philoul check 0 * min5minCarbImpact ???
deviations += Math.max(0 * min5minCarbImpact, csfGlucose[i].deviation)
mealCarbs = Math.max(mealCarbs, csfGlucose[i].mealCarbs)
}
}
// at midnight, write down the mealcarbs as total meal carbs (to prevent special case of when only one meal and it not finishing absorbing by midnight)
// TODO: figure out what to do with dinner carbs that don't finish absorbing by midnight
if (totalMealCarbs == 0) {
totalMealCarbs += mealCarbs
}
if (totalDeviations == 0.0) {
totalDeviations += deviations
}
//log.debug(totalDeviations, totalMealCarbs);
fullNewCSF = if (totalMealCarbs == 0) {
// if no meals today, csf is unchanged
csf
} else {
// how much change would be required to account for all of the deviations
Round.roundTo(totalDeviations / totalMealCarbs, 0.01)
}
// only adjust by 20%
var newCSF = 0.8 * csf + 0.2 * fullNewCSF
// safety cap csf
if (pumpCSF != 0.0) {
val maxCSF = pumpCSF * autotuneMax
val minCSF = pumpCSF * autotuneMin
if (newCSF > maxCSF) {
log("Limiting csf to " + Round.roundTo(maxCSF, 0.01) + " (which is " + autotuneMax + "* pump csf of " + pumpCSF + ")")
newCSF = maxCSF
} else if (newCSF < minCSF) {
log("Limiting csf to " + Round.roundTo(minCSF, 0.01) + " (which is" + autotuneMin + "* pump csf of " + pumpCSF + ")")
newCSF = minCSF
} //else { log.debug("newCSF",newCSF,"is close enough to",pumpCSF); }
}
val oldCSF = Round.roundTo(csf, 0.001)
newCSF = Round.roundTo(newCSF, 0.001)
totalDeviations = Round.roundTo(totalDeviations, 0.001)
log("totalMealCarbs: $totalMealCarbs totalDeviations: $totalDeviations oldCSF $oldCSF fullNewCSF: $fullNewCSF newCSF: $newCSF")
// this is where csf is set based on the outputs
//if (newCSF != 0.0) {
// csf = newCSF
//}
var fullNewCR: Double
fullNewCR = if (totalCR == 0.0) {
// if no meals today, CR is unchanged
carbRatio
} else {
// how much change would be required to account for all of the deviations
totalCR
}
// don't tune CR out of bounds
var maxCR = pumpCarbRatio * autotuneMax
if (maxCR > 150) {
maxCR = 150.0
}
var minCR = pumpCarbRatio * autotuneMin
if (minCR < 3) {
minCR = 3.0
}
// safety cap fullNewCR
if (pumpCarbRatio != 0.0) {
if (fullNewCR > maxCR) {
log("Limiting fullNewCR from " + fullNewCR + " to " + Round.roundTo(maxCR, 0.01) + " (which is " + autotuneMax + " * pump CR of " + pumpCarbRatio + ")")
fullNewCR = maxCR
} else if (fullNewCR < minCR) {
log("Limiting fullNewCR from " + fullNewCR + " to " + Round.roundTo(minCR, 0.01) + " (which is " + autotuneMin + " * pump CR of " + pumpCarbRatio + ")")
fullNewCR = minCR
} //else { log.debug("newCR",newCR,"is close enough to",pumpCarbRatio); }
}
// only adjust by 20%
var newCR = 0.8 * carbRatio + 0.2 * fullNewCR
// safety cap newCR
if (pumpCarbRatio != 0.0) {
if (newCR > maxCR) {
log("Limiting CR to " + Round.roundTo(maxCR, 0.01) + " (which is " + autotuneMax + " * pump CR of " + pumpCarbRatio + ")")
newCR = maxCR
} else if (newCR < minCR) {
log("Limiting CR to " + Round.roundTo(minCR, 0.01) + " (which is " + autotuneMin + " * pump CR of " + pumpCarbRatio + ")")
newCR = minCR
} //else { log.debug("newCR",newCR,"is close enough to",pumpCarbRatio); }
}
newCR = Round.roundTo(newCR, 0.001)
log("oldCR: $carbRatio fullNewCR: $fullNewCR newCR: $newCR")
// this is where CR is set based on the outputs
//var ISFFromCRAndCSF = isf;
if (newCR != 0.0) {
carbRatio = newCR
//ISFFromCRAndCSF = Math.round( carbRatio * csf * 1000)/1000;
}
// calculate median deviation and bgi in data attributable to isf
val isfDeviations: MutableList<Double> = ArrayList()
val bGIs: MutableList<Double> = ArrayList()
val avgDeltas: MutableList<Double> = ArrayList()
val ratios: MutableList<Double> = ArrayList()
var count = 0
for (i in isfGlucose.indices) {
val deviation = isfGlucose[i].deviation
isfDeviations.add(deviation)
val BGI = isfGlucose[i].bgi
bGIs.add(BGI)
val avgDelta = isfGlucose[i].avgDelta
avgDeltas.add(avgDelta)
val ratio = 1 + deviation / BGI
//log.debug("Deviation:",deviation,"BGI:",BGI,"avgDelta:",avgDelta,"ratio:",ratio);
ratios.add(ratio)
count++
}
Collections.sort(avgDeltas)
Collections.sort(bGIs)
Collections.sort(isfDeviations)
Collections.sort(ratios)
var p50deviation = IobCobCalculatorPlugin.percentile(isfDeviations.toTypedArray(), 0.50)
var p50BGI = IobCobCalculatorPlugin.percentile(bGIs.toTypedArray(), 0.50)
val p50ratios = Round.roundTo(IobCobCalculatorPlugin.percentile(ratios.toTypedArray(), 0.50), 0.001)
var fullNewISF = isf
if (count < 10) {
// leave isf unchanged if fewer than 5 isf data points
log("Only found " + isfGlucose.size + " ISF data points, leaving ISF unchanged at " + isf)
} else {
// calculate what adjustments to isf would have been necessary to bring median deviation to zero
fullNewISF = isf * p50ratios
}
fullNewISF = Round.roundTo(fullNewISF, 0.001)
// adjust the target isf to be a weighted average of fullNewISF and pumpISF
val adjustmentFraction: Double
/*
// TODO: philoul may be allow adjustmentFraction in settings with safety limits ?)
if (typeof(pumpProfile.autotune_isf_adjustmentFraction) !== 'undefined') {
adjustmentFraction = pumpProfile.autotune_isf_adjustmentFraction;
} else {*/
adjustmentFraction = 1.0
// }
// low autosens ratio = high isf
val maxISF = pumpISF / autotuneMin
// high autosens ratio = low isf
val minISF = pumpISF / autotuneMax
var adjustedISF = 0.0
var newISF = 0.0
if (pumpISF != 0.0) {
adjustedISF = if (fullNewISF < 0) {
isf
} else {
adjustmentFraction * fullNewISF + (1 - adjustmentFraction) * pumpISF
}
// cap adjustedISF before applying 10%
//log.debug(adjustedISF, maxISF, minISF);
if (adjustedISF > maxISF) {
log("Limiting adjusted isf of " + Round.roundTo(adjustedISF, 0.01) + " to " + Round.roundTo(maxISF, 0.01) + "(which is pump isf of " + pumpISF + "/" + autotuneMin + ")")
adjustedISF = maxISF
} else if (adjustedISF < minISF) {
log("Limiting adjusted isf of" + Round.roundTo(adjustedISF, 0.01) + " to " + Round.roundTo(minISF, 0.01) + "(which is pump isf of " + pumpISF + "/" + autotuneMax + ")")
adjustedISF = minISF
}
// and apply 20% of that adjustment
newISF = 0.8 * isf + 0.2 * adjustedISF
if (newISF > maxISF) {
log("Limiting isf of" + Round.roundTo(newISF, 0.01) + "to" + Round.roundTo(maxISF, 0.01) + "(which is pump isf of" + pumpISF + "/" + autotuneMin + ")")
newISF = maxISF
} else if (newISF < minISF) {
log("Limiting isf of" + Round.roundTo(newISF, 0.01) + "to" + Round.roundTo(minISF, 0.01) + "(which is pump isf of" + pumpISF + "/" + autotuneMax + ")")
newISF = minISF
}
}
newISF = Round.roundTo(newISF, 0.001)
//log.debug(avgRatio);
//log.debug(newISF);
p50deviation = Round.roundTo(p50deviation, 0.001)
p50BGI = Round.roundTo(p50BGI, 0.001)
adjustedISF = Round.roundTo(adjustedISF, 0.001)
log("p50deviation: $p50deviation p50BGI $p50BGI p50ratios: $p50ratios Old isf: $isf fullNewISF: $fullNewISF adjustedISF: $adjustedISF newISF: $newISF")
if (newISF != 0.0) {
isf = newISF
}
previousAutotune.from = preppedGlucose.from
previousAutotune.basal = basalProfile
previousAutotune.isf = isf
previousAutotune.ic = Round.roundTo(carbRatio, 0.001)
previousAutotune.basalUntuned = basalUntuned
previousAutotune.dia = newDia
previousAutotune.peak = newPeak
val localInsulin = LocalInsulin("Ins_$newPeak-$newDia", newPeak, newDia)
previousAutotune.localInsulin = localInsulin
previousAutotune.updateProfile()
return previousAutotune
}
private fun log(message: String) {
autotuneFS.atLog("[Core] $message")
}
}

View file

@ -0,0 +1,204 @@
package info.nightscout.androidaps.plugins.general.autotune
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.plugins.general.autotune.data.ATProfile
import info.nightscout.androidaps.plugins.general.autotune.data.PreppedGlucose
import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils
import info.nightscout.androidaps.R
import org.json.JSONException
import org.slf4j.LoggerFactory
import java.io.*
import java.text.SimpleDateFormat
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AutotuneFS @Inject constructor(
private val rh: ResourceHelper,
private val loggerUtils: LoggerUtils
) {
val AUTOTUNEFOLDER = "autotune"
val SETTINGSFOLDER = "settings"
val RECOMMENDATIONS = "autotune_recommendations.log"
val ENTRIESPREF = "aaps-entries."
val TREATMENTSPREF = "aaps-treatments."
val AAPSBOLUSESPREF = "aaps-boluses."
val PREPPEDPREF = "aaps-autotune."
val SETTINGS = "settings.json"
val PROFIL = "profil"
val PUMPPROFILE = "pumpprofile.json"
val TUNEDPROFILE = "newaapsprofile."
val LOGPREF = "autotune."
val ZIPPREF = "autotune_"
lateinit var autotunePath: File
lateinit var autotuneSettings: File
private var logString = ""
val BUFFER_SIZE = 2048
private val log = LoggerFactory.getLogger(AutotunePlugin::class.java)
/*****************************************************************************
* Create autotune folder for all files created during an autotune session
*****************************************************************************/
fun createAutotuneFolder() {
//create autotune subfolder for autotune files if not exists
autotunePath = File(loggerUtils.logDirectory, AUTOTUNEFOLDER)
if (!(autotunePath.exists() && autotunePath.isDirectory)) {
autotunePath.mkdir()
log("Create $AUTOTUNEFOLDER subfolder in ${loggerUtils.logDirectory}")
}
autotuneSettings = File(loggerUtils.logDirectory, SETTINGSFOLDER)
if (!(autotuneSettings.exists() && autotuneSettings.isDirectory)) {
autotuneSettings.mkdir()
log("Create $SETTINGSFOLDER subfolder in ${loggerUtils.logDirectory}")
}
}
/*****************************************************************************
* between each run of autotune, clean autotune folder content
*****************************************************************************/
fun deleteAutotuneFiles() {
autotunePath.listFiles()?.let { listFiles ->
for (file in listFiles) {
if (file.isFile) file.delete()
}
}
autotuneSettings.listFiles()?.let { listFiles ->
for (file in listFiles) {
if (file.isFile) file.delete()
}
}
log("Delete previous Autotune files")
}
/*****************************************************************************
* Create a JSON autotune files or settings files
*****************************************************************************/
fun exportSettings(settings: String) {
createAutotunefile(SETTINGS, settings, true)
}
fun exportPumpProfile(profile: ATProfile) {
createAutotunefile(PUMPPROFILE, profile.profiletoOrefJSON(), true)
createAutotunefile(PUMPPROFILE, profile.profiletoOrefJSON())
}
fun exportTunedProfile(tunedProfile: ATProfile) {
createAutotunefile(TUNEDPROFILE + formatDate(tunedProfile.from) + ".json", tunedProfile.profiletoOrefJSON())
try {
createAutotunefile(rh.gs(R.string.autotune_tunedprofile_name) + ".json", tunedProfile.profiletoOrefJSON(), true)
} catch (e: JSONException) {
}
}
fun exportEntries(autotuneIob: AutotuneIob) {
try {
createAutotunefile(ENTRIESPREF + formatDate(autotuneIob.startBG) + ".json", autotuneIob.glucoseToJSON())
} catch (e: JSONException) {
}
}
fun exportTreatments(autotuneIob: AutotuneIob) {
try {
createAutotunefile(TREATMENTSPREF + formatDate(autotuneIob.startBG) + ".json", autotuneIob.nsHistoryToJSON())
createAutotunefile(AAPSBOLUSESPREF + formatDate(autotuneIob.startBG) + ".json", autotuneIob.bolusesToJSON())
} catch (e: JSONException) {
}
}
fun exportPreppedGlucose(preppedGlucose: PreppedGlucose) {
createAutotunefile(PREPPEDPREF + formatDate(preppedGlucose.from) + ".json", preppedGlucose.toString(2))
}
fun exportResult(result: String) {
createAutotunefile(RECOMMENDATIONS, result)
}
fun exportLog(lastRun: Long, index: Int = 0) {
val suffix = if (index == 0) "" else "_" + index
log("Create " + LOGPREF + formatDate(lastRun) + suffix + ".log" + " file in " + AUTOTUNEFOLDER + " folder")
createAutotunefile(LOGPREF + formatDate(lastRun) + suffix + ".log", logString)
logString = ""
}
fun exportLogAndZip(lastRun: Long) {
log("Create " + LOGPREF + formatDate(lastRun) + ".log" + " file in " + AUTOTUNEFOLDER + " folder")
createAutotunefile(LOGPREF + formatDate(lastRun) + ".log", logString)
zipAutotune(lastRun)
logString = ""
}
private fun createAutotunefile(fileName: String, stringFile: String, isSettingFile: Boolean = false) {
val autotuneFile = File(if (isSettingFile) autotuneSettings.absolutePath else autotunePath.absolutePath, fileName)
try {
val fw = FileWriter(autotuneFile)
val pw = PrintWriter(fw)
pw.println(stringFile)
pw.close()
fw.close()
log("Create " + fileName + " file in " + (if (isSettingFile) SETTINGSFOLDER else AUTOTUNEFOLDER) + " folder")
} catch (e: FileNotFoundException) {
//log.error("Unhandled exception", e);
} catch (e: IOException) {
//log.error("Unhandled exception", e);
}
}
/**********************************************************************************
* create a zip file with all autotune files and settings in autotune folder at the end of run
**********************************************************************************/
fun zipAutotune(lastRun: Long) {
try {
val zipFileName = ZIPPREF + formatDate(lastRun, true) + ".zip"
val zipFile = File(loggerUtils.logDirectory, zipFileName)
val out = ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFile)))
zipDirectory(autotunePath, autotunePath.name, out)
zipDirectory(autotuneSettings, autotuneSettings.name, out)
out.flush()
out.close()
log("Create $zipFileName file in ${loggerUtils.logDirectory} folder")
} catch (e: IOException) {
//log.error("Unhandled exception", e);
}
}
private fun log(message: String) {
atLog("[FS] $message")
}
fun atLog(message: String) {
logString += "$message\n"
log.debug(message)
}
private fun zipDirectory(folder: File, parentFolder: String, out: ZipOutputStream) {
folder.listFiles()?.let { listFiles ->
for (file in listFiles) {
if (file.isDirectory) {
zipDirectory(file, parentFolder + "/" + file.name, out)
continue
}
try {
out.putNextEntry(ZipEntry(parentFolder + "/" + file.name))
val bis = BufferedInputStream(FileInputStream(file))
//long bytesRead = 0;
val bytesIn = ByteArray(BUFFER_SIZE)
var read: Int
while (bis.read(bytesIn).also { read = it } != -1) {
out.write(bytesIn, 0, read)
}
out.closeEntry()
} catch (e: IOException) {
//log.error("Unhandled exception", e);
}
}
}
}
private fun formatDate(date: Long, dateHour: Boolean = false): String {
val dateFormat = if (dateHour) SimpleDateFormat("yyyy-MM-dd_HH-mm-ss") else SimpleDateFormat("yyyy-MM-dd")
return dateFormat.format(date)
}
}

View file

@ -0,0 +1,500 @@
package info.nightscout.androidaps.plugins.general.autotune
import android.graphics.Typeface
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.TableLayout
import android.widget.TableRow
import android.widget.TextView
import dagger.android.HasAndroidInjector
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.AutotuneFragmentBinding
import info.nightscout.androidaps.dialogs.ProfileViewerDialog
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.autotune.data.ATProfile
import info.nightscout.androidaps.plugins.general.autotune.events.EventAutotuneUpdateGui
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged
import info.nightscout.androidaps.data.LocalInsulin
import info.nightscout.androidaps.data.ProfileSealed
import info.nightscout.androidaps.database.entities.UserEntry
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.extensions.runOnUiThread
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.MidnightTime
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.alertDialogs.OKDialog.showConfirmation
import info.nightscout.shared.SafeParse
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import org.json.JSONObject
//import org.slf4j.LoggerFactory
import java.text.DecimalFormat
import java.util.*
import javax.inject.Inject
class AutotuneFragment : DaggerFragment() {
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var autotunePlugin: AutotunePlugin
@Inject lateinit var autotuneFS: AutotuneFS
@Inject lateinit var sp: SP
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var localProfilePlugin: LocalProfilePlugin
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var rh: ResourceHelper
@Inject lateinit var rxBus: RxBus
@Inject lateinit var injector: HasAndroidInjector
private var disposable: CompositeDisposable = CompositeDisposable()
//private val log = LoggerFactory.getLogger(AutotunePlugin::class.java)
private var _binding: AutotuneFragmentBinding? = null
private lateinit var profileStore: ProfileStore
private var profileName = ""
private lateinit var profile: ATProfile
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = AutotuneFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
autotunePlugin.lastRun = sp.getLong(R.string.key_autotune_last_run, 0)
if (autotunePlugin.lastNbDays.isEmpty())
autotunePlugin.lastNbDays = sp.getInt(R.string.key_autotune_default_tune_days, 5).toString()
val defaultValue = sp.getInt(R.string.key_autotune_default_tune_days, 5).toDouble()
profileStore = activePlugin.activeProfileSource.profile ?: ProfileStore(injector, JSONObject(), dateUtil)
profileName = if (binding.profileList.text.toString() == rh.gs(R.string.active)) "" else binding.profileList.text.toString()
profileFunction.getProfile()?.let { currentProfile ->
profile = ATProfile(profileStore.getSpecificProfile(profileName)?.let { ProfileSealed.Pure(it) } ?:currentProfile, LocalInsulin(""), injector)
}
binding.tuneDays.setParams(
savedInstanceState?.getDouble("tunedays")
?: defaultValue, 1.0, 30.0, 1.0, DecimalFormat("0"), false, null, textWatcher)
binding.autotuneRun.setOnClickListener {
val daysBack = SafeParse.stringToInt(binding.tuneDays.text)
autotunePlugin.calculationRunning = true
autotunePlugin.lastNbDays = daysBack.toString()
log("Run Autotune $profileName, $daysBack days")
Thread {
autotunePlugin.aapsAutotune(daysBack, false, profileName)
}.start()
updateGui()
}
binding.profileList.onItemClickListener = AdapterView.OnItemClickListener { _, _, _, _ ->
if (!autotunePlugin.calculationRunning)
{
profileName = if (binding.profileList.text.toString() == rh.gs(R.string.active)) "" else binding.profileList.text.toString()
profileFunction.getProfile()?.let { currentProfile ->
profile = ATProfile(profileStore.getSpecificProfile(profileName)?.let { ProfileSealed.Pure(it) } ?:currentProfile, LocalInsulin(""), injector)
}
autotunePlugin.selectedProfile = profileName
resetParam()
}
updateGui()
}
binding.autotuneCopylocal.setOnClickListener {
val localName = rh.gs(R.string.autotune_tunedprofile_name) + " " + dateUtil.dateAndTimeString(autotunePlugin.lastRun)
val circadian = sp.getBoolean(R.string.key_autotune_circadian_ic_isf, false)
autotunePlugin.tunedProfile?.let { tunedProfile ->
showConfirmation(requireContext(),
rh.gs(R.string.autotune_copy_localprofile_button),
rh.gs(R.string.autotune_copy_local_profile_message) + "\n" + localName + " " + dateUtil.dateAndTimeString(autotunePlugin.lastRun),
Runnable {
localProfilePlugin.addProfile(localProfilePlugin.copyFrom(tunedProfile.getProfile(circadian), localName))
rxBus.send(EventLocalProfileChanged())
uel.log(
UserEntry.Action.NEW_PROFILE,
UserEntry.Sources.Autotune,
ValueWithUnit.SimpleString(localName)
)
updateGui()
})
}
}
binding.autotuneUpdateProfile.setOnClickListener {
val localName = autotunePlugin.pumpProfile.profilename
showConfirmation(requireContext(),
rh.gs(R.string.autotune_update_input_profile_button),
rh.gs(R.string.autotune_update_local_profile_message, localName),
Runnable {
autotunePlugin.tunedProfile?.profilename = localName
autotunePlugin.updateProfile(autotunePlugin.tunedProfile)
autotunePlugin.updateButtonVisibility = View.GONE
uel.log(
UserEntry.Action.STORE_PROFILE,
UserEntry.Sources.Autotune,
ValueWithUnit.SimpleString(localName)
)
updateGui()
}
)
}
binding.autotuneRevertProfile.setOnClickListener {
val localName = autotunePlugin.pumpProfile.profilename
showConfirmation(requireContext(),
rh.gs(R.string.autotune_revert_input_profile_button),
rh.gs(R.string.autotune_revert_local_profile_message, localName),
Runnable {
autotunePlugin.tunedProfile?.profilename = ""
autotunePlugin.updateProfile(autotunePlugin.pumpProfile)
autotunePlugin.updateButtonVisibility = View.VISIBLE
uel.log(
UserEntry.Action.STORE_PROFILE,
UserEntry.Sources.Autotune,
ValueWithUnit.SimpleString(localName)
)
updateGui()
}
)
}
binding.autotuneCheckInputProfile.setOnClickListener {
val pumpProfile = profileFunction.getProfile()?.let { currentProfile ->
profileStore.getSpecificProfile(profileName)?.let { specificProfile ->
ATProfile(ProfileSealed.Pure(specificProfile), LocalInsulin(""), injector).also {
it.profilename = profileName
}
}
?: ATProfile(currentProfile, LocalInsulin(""), injector).also {
it.profilename = profileFunction.getProfileName()
}
}
pumpProfile?.let {
ProfileViewerDialog().also { pvd ->
pvd.arguments = Bundle().also {
it.putLong("time", dateUtil.now())
it.putInt("mode", ProfileViewerDialog.Mode.CUSTOM_PROFILE.ordinal)
it.putString("customProfile", pumpProfile.profile.toPureNsJson(dateUtil).toString())
it.putString("customProfileUnits", profileFunction.getUnits().asText)
it.putString("customProfileName", pumpProfile.profilename)
}
}.show(childFragmentManager, "ProfileViewDialog")
}
}
binding.autotuneCompare.setOnClickListener {
val pumpProfile = autotunePlugin.pumpProfile
val circadian = sp.getBoolean(R.string.key_autotune_circadian_ic_isf, false)
val tunedprofile = if (circadian) autotunePlugin.tunedProfile?.circadianProfile else autotunePlugin.tunedProfile?.profile
ProfileViewerDialog().also { pvd ->
pvd.arguments = Bundle().also {
it.putLong("time", dateUtil.now())
it.putInt("mode", ProfileViewerDialog.Mode.PROFILE_COMPARE.ordinal)
it.putString("customProfile", pumpProfile.profile.toPureNsJson(dateUtil).toString())
it.putString("customProfile2", tunedprofile?.toPureNsJson(dateUtil).toString())
it.putString("customProfileUnits", profileFunction.getUnits().asText)
it.putString("customProfileName", pumpProfile.profilename + "\n" + rh.gs(R.string.autotune_tunedprofile_name))
}
}.show(childFragmentManager, "ProfileViewDialog")
}
binding.autotuneProfileswitch.setOnClickListener {
val tunedProfile = autotunePlugin.tunedProfile
autotunePlugin.updateProfile(tunedProfile)
val circadian = sp.getBoolean(R.string.key_autotune_circadian_ic_isf, false)
tunedProfile?.let { tunedP ->
tunedP.profileStore(circadian)?.let {
showConfirmation(requireContext(),
rh.gs(R.string.activate_profile) + ": " + tunedP.profilename + " ?",
Runnable {
uel.log(
UserEntry.Action.STORE_PROFILE,
UserEntry.Sources.Autotune,
ValueWithUnit.SimpleString(tunedP.profilename)
)
val now = dateUtil.now()
if (profileFunction.createProfileSwitch(
it,
profileName = tunedP.profilename,
durationInMinutes = 0,
percentage = 100,
timeShiftInHours = 0,
timestamp = now
)
) {
uel.log(
UserEntry.Action.PROFILE_SWITCH,
UserEntry.Sources.Autotune,
"Autotune AutoSwitch",
ValueWithUnit.SimpleString(autotunePlugin.tunedProfile!!.profilename)
)
}
rxBus.send(EventLocalProfileChanged())
updateGui()
}
)
}
}
}
}
@Synchronized
override fun onResume() {
super.onResume()
disposable += rxBus
.toObservable(EventAutotuneUpdateGui::class.java)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
updateGui()
}, { fabricPrivacy.logException(it) })
checkNewDay()
binding.tuneDays.value = autotunePlugin.lastNbDays.toDouble()
updateGui()
}
@Synchronized
override fun onPause() {
super.onPause()
disposable.clear()
}
@Synchronized
private fun updateGui() {
_binding ?: return
profileStore = activePlugin.activeProfileSource.profile ?: ProfileStore(injector, JSONObject(), dateUtil)
profileName = if (binding.profileList.text.toString() == rh.gs(R.string.active)) "" else binding.profileList.text.toString()
profileFunction.getProfile()?.let { currentProfile ->
profile = ATProfile(profileStore.getSpecificProfile(profileName)?.let { ProfileSealed.Pure(it) } ?:currentProfile, LocalInsulin(""), injector)
}
val profileList: ArrayList<CharSequence> = profileStore.getProfileList()
profileList.add(0, rh.gs(R.string.active))
context?.let { context ->
binding.profileList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, profileList))
} ?: return
// set selected to actual profile
if (autotunePlugin.selectedProfile.isNotEmpty())
binding.profileList.setText(autotunePlugin.selectedProfile, false)
else {
binding.profileList.setText(profileList[0], false)
}
binding.autotuneRun.visibility = View.GONE
binding.autotuneCheckInputProfile.visibility = View.GONE
binding.autotuneCopylocal.visibility = View.GONE
binding.autotuneUpdateProfile.visibility = View.GONE
binding.autotuneRevertProfile.visibility = View.GONE
binding.autotuneProfileswitch.visibility = View.GONE
binding.autotuneCompare.visibility = View.GONE
when {
autotunePlugin.calculationRunning -> {
binding.tuneWarning.text = rh.gs(R.string.autotune_warning_during_run)
}
autotunePlugin.lastRunSuccess -> {
binding.autotuneCopylocal.visibility = View.VISIBLE
binding.autotuneUpdateProfile.visibility = autotunePlugin.updateButtonVisibility
binding.autotuneRevertProfile.visibility = if (autotunePlugin.updateButtonVisibility == View.VISIBLE) View.GONE else View.VISIBLE
binding.autotuneProfileswitch.visibility = View.VISIBLE
binding.tuneWarning.text = rh.gs(R.string.autotune_warning_after_run)
binding.autotuneCompare.visibility = View.VISIBLE
}
else -> {
binding.autotuneRun.visibility = View.VISIBLE
binding.autotuneCheckInputProfile.visibility = View.VISIBLE
}
}
binding.tuneLastrun.text = dateUtil.dateAndTimeString(autotunePlugin.lastRun)
showResults()
}
private fun checkNewDay() {
val runToday = autotunePlugin.lastRun > MidnightTime.calc(dateUtil.now() - autotunePlugin.autotuneStartHour * 3600 * 1000L) + autotunePlugin.autotuneStartHour * 3600 * 1000L
if (runToday && autotunePlugin.result != "")
{
binding.tuneWarning.text = rh.gs(R.string.autotune_warning_after_run)
} else if (!runToday || autotunePlugin.result.isEmpty()) { //if new day reinit result, default days, warning and button's visibility
resetParam(!runToday)
}
}
private fun addWarnings(): String {
var warning = ""
var nl = ""
if (profileFunction.getProfile() == null) {
warning = rh.gs(R.string.profileswitch_ismissing)
return warning
}
profileFunction.getProfile()?.let {
if (!profile.isValid) return rh.gs(R.string.autotune_profile_invalid)
if (profile.icSize > 1) {
warning += nl + rh.gs(R.string.autotune_ic_warning, profile.icSize, profile.ic)
nl = "\n"
}
if (profile.isfSize > 1) {
warning += nl + rh.gs(R.string.autotune_isf_warning, profile.isfSize, Profile.fromMgdlToUnits(profile.isf, profileFunction.getUnits()), profileFunction.getUnits().asText)
}
}
return warning
}
private fun resetParam(resetDay: Boolean = true) {
binding.tuneWarning.text = addWarnings()
if (resetDay)
autotunePlugin.lastNbDays = sp.getInt(R.string.key_autotune_default_tune_days, 5).toString()
autotunePlugin.result = ""
binding.autotuneResults.removeAllViews()
autotunePlugin.tunedProfile = null
autotunePlugin.lastRunSuccess = false
autotunePlugin.updateButtonVisibility = View.GONE
}
private val textWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable) { updateGui() }
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (binding.tuneDays.text.isNotEmpty()) {
try {
if (autotunePlugin.calculationRunning)
binding.tuneDays.value = autotunePlugin.lastNbDays.toDouble()
if (binding.tuneDays.value != autotunePlugin.lastNbDays.toDouble()) {
autotunePlugin.lastNbDays = binding.tuneDays.text
resetParam(false)
}
} catch (e:Exception) { }
}
}
}
private fun showResults() {
Thread {
runOnUiThread {
binding.autotuneResults.removeAllViews()
if (autotunePlugin.result.isNotBlank()) {
var toMgDl = 1.0
if (profileFunction.getUnits() == GlucoseUnit.MMOL) toMgDl = Constants.MMOLL_TO_MGDL
var isf_Format = if (profileFunction.getUnits() == GlucoseUnit.MMOL) "%.2f" else "%.1f"
binding.autotuneResults.addView(
TableLayout(context).also { layout ->
layout.addView(
TextView(context).apply {
text = autotunePlugin.result
setTypeface(typeface, Typeface.BOLD)
gravity = Gravity.CENTER_HORIZONTAL
setTextAppearance(android.R.style.TextAppearance_Material_Medium)
})
autotunePlugin.tunedProfile?.let { tuned ->
layout.addView(toTableRowHeader())
val tuneInsulin = sp.getBoolean(R.string.key_autotune_tune_insulin_curve, false)
if (tuneInsulin) {
layout.addView(toTableRowValue(rh.gs(R.string.insulin_peak), autotunePlugin.pumpProfile.localInsulin.peak.toDouble(), tuned.localInsulin.peak.toDouble(), "%.0f"))
layout.addView(toTableRowValue(rh.gs(R.string.dia), Round.roundTo(autotunePlugin.pumpProfile.localInsulin.dia, 0.1), Round.roundTo(tuned.localInsulin.dia, 0.1),"%.1f"))
}
layout.addView(toTableRowValue(rh.gs(R.string.isf_short), Round.roundTo(autotunePlugin.pumpProfile.isf / toMgDl, 0.001), Round.roundTo(tuned.isf / toMgDl, 0.001), isf_Format))
layout.addView(toTableRowValue(rh.gs(R.string.ic_short), Round.roundTo(autotunePlugin.pumpProfile.ic, 0.001), Round.roundTo(tuned.ic, 0.001), "%.2f"))
layout.addView(
TextView(context).apply {
text = rh.gs(R.string.basal)
setTypeface(typeface, Typeface.BOLD)
gravity = Gravity.CENTER_HORIZONTAL
setTextAppearance(android.R.style.TextAppearance_Material_Medium)
}
)
layout.addView(toTableRowHeader(true))
var totalPump = 0.0
var totalTuned = 0.0
for (h in 0 until tuned.basal.size) {
val df = DecimalFormat("00")
val time = df.format(h.toLong()) + ":00"
totalPump += autotunePlugin.pumpProfile.basal[h]
totalTuned += tuned.basal[h]
layout.addView(toTableRowValue(time, autotunePlugin.pumpProfile.basal[h], tuned.basal[h], "%.3f", tuned.basalUntuned[h].toString()))
}
layout.addView(toTableRowValue("", totalPump, totalTuned, " "))
}
}
)
}
binding.autotuneResultsCard.visibility = if (autotunePlugin.calculationRunning && autotunePlugin.result.isEmpty()) View.GONE else View.VISIBLE
}
}.start()
}
private fun toTableRowHeader(basal:Boolean = false): TableRow =
TableRow(context).also { header ->
val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT).apply { weight = 1f }
header.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT).apply { gravity = Gravity.CENTER_HORIZONTAL }
header.addView(TextView(context).apply {
layoutParams = lp.apply { column = 0 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = if (basal) rh.gs(R.string.time) else rh.gs(R.string.autotune_param)
})
header.addView(TextView(context).apply {
layoutParams = lp.apply { column = 1 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = rh.gs(R.string.profile)
})
header.addView(TextView(context).apply {
layoutParams = lp.apply { column = 2 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = rh.gs(R.string.autotune_tunedprofile_name)
})
header.addView(TextView(context).apply {
layoutParams = lp.apply { column = 3 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = rh.gs(R.string.autotune_percent)
})
header.addView(TextView(context).apply {
layoutParams = lp.apply { column = 4 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = if (basal) rh.gs(R.string.autotune_missing) else " "
})
}
private fun toTableRowValue(hour: String, inputValue: Double, tunedValue: Double, format:String = "%.3f", missing: String = ""): TableRow =
TableRow(context).also { row ->
val percentValue = Round.roundTo(tunedValue / inputValue * 100 - 100, 1.0).toInt().toString() + "%"
val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT).apply { weight = 1f }
row.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT).apply { gravity = Gravity.CENTER_HORIZONTAL }
row.addView(TextView(context).apply {
layoutParams = lp.apply { column = 0 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = hour
})
row.addView(TextView(context).apply {
layoutParams = lp.apply { column = 1 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = String.format(format, inputValue)
})
row.addView(TextView(context).apply {
layoutParams = lp.apply { column = 2 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = String.format(format, tunedValue)
})
row.addView(TextView(context).apply {
layoutParams = lp.apply { column = 3 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = percentValue
})
row.addView(TextView(context).apply {
layoutParams = lp.apply { column = 4 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = missing
})
}
private fun log(message: String) {
autotuneFS.atLog("[Fragment] $message")
}
}

View file

@ -0,0 +1,386 @@
package info.nightscout.androidaps.plugins.general.autotune
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.*
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.embedments.InterfaceIDs
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.database.entities.*
import info.nightscout.androidaps.extensions.durationInMinutes
import info.nightscout.androidaps.extensions.iobCalc
import info.nightscout.androidaps.extensions.toJson
import info.nightscout.androidaps.extensions.toTemporaryBasal
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.plugins.general.autotune.data.ATProfile
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.T
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray
import org.json.JSONObject
import org.slf4j.LoggerFactory
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.roundToInt
@Singleton
class AutotuneIob @Inject constructor(
private val aapsLogger: AAPSLogger,
private val repository: AppRepository,
private val profileFunction: ProfileFunction,
private val sp: SP,
private val dateUtil: DateUtil,
private val activePlugin: ActivePlugin,
private val autotuneFS: AutotuneFS
) {
private val nsTreatments = ArrayList<NsTreatment>()
private var dia: Double = Constants.defaultDIA
var boluses: MutableList<Bolus> = ArrayList()
var meals = ArrayList<Carbs>()
lateinit var glucose: List<GlucoseValue> // newest at index 0
private lateinit var tempBasals: MutableList<TemporaryBasal>
var startBG: Long = 0
var endBG: Long = 0
private fun range(): Long = (60 * 60 * 1000L * dia + T.hours(2).msecs()).toLong()
fun initializeData(from: Long, to: Long, tunedProfile: ATProfile) {
dia = tunedProfile.dia
startBG = from
endBG = to
nsTreatments.clear()
tempBasals = ArrayList<TemporaryBasal>()
initializeBgreadings(from, to)
initializeTreatmentData(from - range(), to)
initializeTempBasalData(from - range(), to, tunedProfile)
initializeExtendedBolusData(from - range(), to, tunedProfile)
Collections.sort(tempBasals) { o1: TemporaryBasal, o2: TemporaryBasal -> (o2.timestamp - o1.timestamp).toInt() }
// Without Neutral TBR, Autotune Web will ignore iob for periods without TBR running
addNeutralTempBasal(from - range(), to, tunedProfile)
Collections.sort(nsTreatments) { o1: NsTreatment, o2: NsTreatment -> (o2.date - o1.date).toInt() }
Collections.sort(boluses) { o1: Bolus, o2: Bolus -> (o2.timestamp - o1.timestamp).toInt() }
log.debug("D/AutotunePlugin: Nb Treatments: " + nsTreatments.size + " Nb meals: " + meals.size)
}
private fun initializeBgreadings(from: Long, to: Long) {
glucose = repository.compatGetBgReadingsDataFromTime(from, to, false).blockingGet();
}
//nsTreatment is used only for export data, meals is used in AutotunePrep
private fun initializeTreatmentData(from: Long, to: Long) {
val oldestBgDate = if (glucose.size > 0) glucose[glucose.size - 1].timestamp else from
log.debug("AutotunePlugin Check BG date: BG Size: " + glucose.size + " OldestBG: " + dateUtil.dateAndTimeAndSecondsString(oldestBgDate) + " to: " + dateUtil.dateAndTimeAndSecondsString(to))
val tmpCarbs = repository.getCarbsDataFromTimeToTimeExpanded(from, to, false).blockingGet()
log.debug("AutotunePlugin Nb treatments after query: " + tmpCarbs.size)
meals.clear()
boluses.clear()
var nbCarbs = 0
for (i in tmpCarbs.indices) {
val tp = tmpCarbs[i]
if (tp.isValid) {
nsTreatments.add(NsTreatment(tp))
//only carbs after first BGReadings are taken into account in calculation of Autotune
if (tp.amount > 0.0 && tp.timestamp >= oldestBgDate) meals.add(tmpCarbs[i])
if (tp.timestamp < to && tp.amount > 0.0)
nbCarbs++
}
}
val tmpBolus = repository.getBolusesDataFromTimeToTime(from, to, false).blockingGet()
var nbSMB = 0
var nbBolus = 0
for (i in tmpBolus.indices) {
val tp = tmpBolus[i]
if (tp.isValid && tp.type != Bolus.Type.PRIMING) {
boluses.add(tp)
nsTreatments.add(NsTreatment(tp))
//only carbs after first BGReadings are taken into account in calculation of Autotune
if (tp.timestamp < to) {
if (tp.type == Bolus.Type.SMB)
nbSMB++
else if (tp.amount > 0.0)
nbBolus++
}
}
}
//log.debug("AutotunePlugin Nb Meals: $nbCarbs Nb Bolus: $nbBolus Nb SMB: $nbSMB")
}
//nsTreatment is used only for export data
private fun initializeTempBasalData(from: Long, to: Long, tunedProfile: ATProfile) {
val tBRs = repository.getTemporaryBasalsDataFromTimeToTime(from, to, false).blockingGet()
//log.debug("D/AutotunePlugin tempBasal size before cleaning:" + tBRs.size);
for (i in tBRs.indices) {
if (tBRs[i].isValid)
toSplittedTimestampTB(tBRs[i], tunedProfile)
}
//log.debug("D/AutotunePlugin: tempBasal size: " + tempBasals.size)
}
//nsTreatment is used only for export data
private fun initializeExtendedBolusData(from: Long, to: Long, tunedProfile: ATProfile) {
val extendedBoluses = repository.getExtendedBolusDataFromTimeToTime(from, to, false).blockingGet()
val pumpInterface = activePlugin.activePump
if (pumpInterface.isFakingTempsByExtendedBoluses) {
for (i in extendedBoluses.indices) {
val eb = extendedBoluses[i]
if (eb.isValid)
profileFunction.getProfile(eb.timestamp)?.let {
toSplittedTimestampTB(eb.toTemporaryBasal(it), tunedProfile)
}
}
} else {
for (i in extendedBoluses.indices) {
val eb = extendedBoluses[i]
if (eb.isValid) {
nsTreatments.add(NsTreatment(eb))
boluses.addAll(convertToBoluses(eb))
}
}
}
}
// addNeutralTempBasal will add a fake neutral TBR (100%) to have correct basal rate in exported file for periods without TBR running
// to be able to compare results between oref0 algo and aaps
private fun addNeutralTempBasal(from: Long, to: Long, tunedProfile: ATProfile) {
var previousStart = to
for (i in tempBasals.indices) {
val newStart = tempBasals[i].timestamp + tempBasals[i].duration
if (previousStart - newStart > T.mins(1).msecs()) { // fill neutral only if more than 1 min
val neutralTbr = TemporaryBasal(
isValid = true,
isAbsolute = false,
timestamp = newStart,
rate = 100.0,
duration = previousStart - newStart,
interfaceIDs_backing = InterfaceIDs(nightscoutId = "neutral_" + newStart.toString()),
type = TemporaryBasal.Type.NORMAL
)
toSplittedTimestampTB(neutralTbr, tunedProfile)
}
previousStart = tempBasals[i].timestamp
}
if (previousStart - from > T.mins(1).msecs()) { // fill neutral only if more than 1 min
val neutralTbr = TemporaryBasal(
isValid = true,
isAbsolute = false,
timestamp = from,
rate = 100.0,
duration = previousStart - from,
interfaceIDs_backing = InterfaceIDs(nightscoutId = "neutral_" + from.toString()),
type = TemporaryBasal.Type.NORMAL
)
toSplittedTimestampTB(neutralTbr, tunedProfile)
}
}
// toSplittedTimestampTB will split all TBR across hours in different TBR with correct absolute value to be sure to have correct basal rate
// even if profile rate is not the same
private fun toSplittedTimestampTB(tb: TemporaryBasal, tunedProfile: ATProfile) {
var splittedTimestamp = tb.timestamp
val cutInMilliSec = T.mins(30).msecs() //30 min to compare with oref0
var splittedDuration = tb.duration
if (tb.isValid && tb.durationInMinutes > 0) {
val endTimestamp = splittedTimestamp + splittedDuration
while (splittedDuration > 0) {
if (Profile.milliSecFromMidnight(splittedTimestamp) / cutInMilliSec == Profile.milliSecFromMidnight(endTimestamp) / cutInMilliSec) {
val newtb = TemporaryBasal(
isValid = true,
isAbsolute = tb.isAbsolute,
timestamp = splittedTimestamp,
rate = tb.rate,
duration = splittedDuration,
interfaceIDs_backing = tb.interfaceIDs_backing,
type = tb.type
)
tempBasals.add(newtb)
nsTreatments.add(NsTreatment(newtb))
splittedDuration = 0
val profile = profileFunction.getProfile(newtb.timestamp) ?:continue
boluses.addAll(convertToBoluses(newtb, profile, tunedProfile.profile)) //
// required for correct iob calculation with oref0 algo
} else {
val durationFilled = (cutInMilliSec - Profile.milliSecFromMidnight(splittedTimestamp) % cutInMilliSec)
val newtb = TemporaryBasal(
isValid = true,
isAbsolute = tb.isAbsolute,
timestamp = splittedTimestamp,
rate = tb.rate,
duration = durationFilled,
interfaceIDs_backing = tb.interfaceIDs_backing,
type = tb.type
)
tempBasals.add(newtb)
nsTreatments.add(NsTreatment(newtb))
splittedTimestamp += durationFilled
splittedDuration = splittedDuration - durationFilled
val profile = profileFunction.getProfile(newtb.timestamp) ?:continue
boluses.addAll(convertToBoluses(newtb, profile, tunedProfile.profile)) // required for correct iob calculation with oref0 algo
}
}
}
}
fun getIOB(time: Long, localInsulin: LocalInsulin): IobTotal {
val bolusIob = getCalculationToTimeTreatments(time, localInsulin).round()
return bolusIob
}
fun getCalculationToTimeTreatments(time: Long, localInsulin: LocalInsulin): IobTotal {
val total = IobTotal(time)
val detailedLog = sp.getBoolean(R.string.key_autotune_additional_log, false)
for (pos in boluses.indices) {
val t = boluses[pos]
if (!t.isValid) continue
if (t.timestamp > time || t.timestamp < time - localInsulin.duration) continue
val tIOB = t.iobCalc(time, localInsulin)
if (detailedLog)
log("iobCalc;${t.interfaceIDs.nightscoutId};$time;${t.timestamp};${tIOB.iobContrib};${tIOB.activityContrib};${dateUtil.dateAndTimeAndSecondsString(time)};${dateUtil.dateAndTimeAndSecondsString(t.timestamp)}")
total.iob += tIOB.iobContrib
total.activity += tIOB.activityContrib
}
return total
}
fun convertToBoluses(eb: ExtendedBolus): MutableList<Bolus> {
val result: MutableList<Bolus> = ArrayList()
val tempBolusSize = 0.05
val tempBolusCount : Int = (eb.amount / tempBolusSize).roundToInt()
if(tempBolusCount > 0) {
val tempBolusSpacing = eb.duration / tempBolusCount
for (j in 0L until tempBolusCount) {
val calcDate = eb.timestamp + j * tempBolusSpacing
val bolusInterfaceIDs = InterfaceIDs().also { it.nightscoutId = eb.interfaceIDs.nightscoutId + "_eb_$j" }
val tempBolusPart = Bolus(
interfaceIDs_backing = bolusInterfaceIDs,
timestamp = calcDate,
amount = tempBolusSize,
type = Bolus.Type.NORMAL
)
result.add(tempBolusPart)
}
}
return result
}
fun convertToBoluses(tbr: TemporaryBasal, profile: Profile, tunedProfile: Profile): MutableList<Bolus> {
val result: MutableList<Bolus> = ArrayList()
val realDuration = tbr.durationInMinutes
val basalRate = profile.getBasal(tbr.timestamp)
val tunedRate = tunedProfile.getBasal(tbr.timestamp)
val netBasalRate = Round.roundTo(if (tbr.isAbsolute) {
tbr.rate - tunedRate
} else {
tbr.rate / 100.0 * basalRate - tunedRate
}, 0.001)
val tempBolusSize = if (netBasalRate < 0 ) -0.05 else 0.05
val netBasalAmount: Double = Round.roundTo(netBasalRate * realDuration / 60.0, 0.01)
val tempBolusCount : Int = (netBasalAmount / tempBolusSize).roundToInt()
if(tempBolusCount > 0) {
val tempBolusSpacing = realDuration * 60 * 1000 / tempBolusCount
for (j in 0L until tempBolusCount) {
val calcDate = tbr.timestamp + j * tempBolusSpacing
val bolusInterfaceIDs = InterfaceIDs().also { it.nightscoutId = tbr.interfaceIDs.nightscoutId + "_tbr_$j" }
val tempBolusPart = Bolus(
interfaceIDs_backing = bolusInterfaceIDs,
timestamp = calcDate,
amount = tempBolusSize,
type = Bolus.Type.NORMAL
)
result.add(tempBolusPart)
}
}
return result
}
fun glucoseToJSON(): String {
val glucoseJson = JSONArray()
for (bgreading in glucose)
glucoseJson.put(bgreading.toJson(true, dateUtil))
return glucoseJson.toString(2)
}
fun bolusesToJSON(): String {
val bolusesJson = JSONArray()
for (bolus in boluses)
bolusesJson.put(bolus.toJson(true, dateUtil))
return bolusesJson.toString(2)
}
fun nsHistoryToJSON(): String {
val json = JSONArray()
for (t in nsTreatments) {
json.put(t.toJson())
}
return json.toString(2).replace("\\/", "/")
}
//I add this internal class to be able to export easily ns-treatment files with same contain and format than NS query used by oref0-autotune
private inner class NsTreatment {
var date: Long = 0
var eventType: TherapyEvent.Type? = null
var carbsTreatment: Carbs? = null
var bolusTreatment: Bolus? = null
var temporaryBasal: TemporaryBasal? = null
var extendedBolus: ExtendedBolus? = null
constructor(t: Carbs) {
carbsTreatment = t
date = t.timestamp
eventType = TherapyEvent.Type.CARBS_CORRECTION
}
constructor(t: Bolus) {
bolusTreatment = t
date = t.timestamp
eventType = TherapyEvent.Type.CORRECTION_BOLUS
}
constructor(t: TemporaryBasal) {
temporaryBasal = t
date = t.timestamp
eventType = TherapyEvent.Type.TEMPORARY_BASAL
}
constructor(t: ExtendedBolus) {
extendedBolus = t
date = t.timestamp
eventType = TherapyEvent.Type.COMBO_BOLUS
}
fun toJson(): JSONObject? {
val cPjson = JSONObject()
return when (eventType) {
TherapyEvent.Type.TEMPORARY_BASAL ->
temporaryBasal?.let { tbr ->
val profile = profileFunction.getProfile(tbr.timestamp)
profile?.let {
tbr.toJson(true, it, dateUtil)
}
}
TherapyEvent.Type.COMBO_BOLUS ->
extendedBolus?.let {
val profile = profileFunction.getProfile(it.timestamp)
it.toJson(true, profile!!, dateUtil)
}
TherapyEvent.Type.CORRECTION_BOLUS -> bolusTreatment?.toJson(true, dateUtil)
TherapyEvent.Type.CARBS_CORRECTION -> carbsTreatment?.toJson(true, dateUtil)
else -> cPjson
}
}
}
private fun log(message: String) {
autotuneFS.atLog("[iob] $message")
}
companion object {
private val log = LoggerFactory.getLogger(AutotunePlugin::class.java)
}
}

View file

@ -0,0 +1,309 @@
package info.nightscout.androidaps.plugins.general.autotune
import android.view.View
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.LocalInsulin
import info.nightscout.androidaps.data.ProfileSealed
import info.nightscout.androidaps.database.entities.UserEntry
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.autotune.data.ATProfile
import info.nightscout.androidaps.plugins.general.autotune.data.PreppedGlucose
import info.nightscout.androidaps.plugins.general.autotune.events.EventAutotuneUpdateGui
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.MidnightTime
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONException
import org.json.JSONObject
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
/**
* adaptation from oref0 autotune started by philoul on 2020 (complete refactoring of AutotunePlugin initialised by Rumen Georgiev on 1/29/2018.)
*
* TODO: replace Thread by Worker
* TODO: future version: Allow day of the week selection to tune specifics days (training days, working days, WE days)
*/
@Singleton
class AutotunePlugin @Inject constructor(
injector: HasAndroidInjector,
resourceHelper: ResourceHelper,
private val sp: SP,
private val rxBus: RxBus,
private val profileFunction: ProfileFunction,
private val dateUtil: DateUtil,
private val activePlugin: ActivePlugin,
private val localProfilePlugin: LocalProfilePlugin,
private val autotuneFS: AutotuneFS,
private val autotuneIob: AutotuneIob,
private val autotunePrep: AutotunePrep,
private val autotuneCore: AutotuneCore,
private val buildHelper:BuildHelper,
private val uel: UserEntryLogger,
aapsLogger: AAPSLogger
) : PluginBase(PluginDescription()
.mainType(PluginType.GENERAL)
.fragmentClass(AutotuneFragment::class.qualifiedName)
.pluginIcon(R.drawable.ic_autotune)
.pluginName(R.string.autotune)
.shortName(R.string.autotune_shortname)
.preferencesId(R.xml.pref_autotune)
.description(R.string.autotune_description),
aapsLogger, resourceHelper, injector
), Autotune {
@Volatile override var lastRunSuccess: Boolean = false
@Volatile var result: String = ""
@Volatile var calculationRunning: Boolean = false
@Volatile var lastRun: Long = 0
@Volatile var selectedProfile = ""
@Volatile var lastNbDays: String = ""
@Volatile var updateButtonVisibility: Int = 0
@Volatile lateinit var pumpProfile: ATProfile
@Volatile var tunedProfile: ATProfile? = null
private var preppedGlucose: PreppedGlucose? = null
private lateinit var profile: Profile
val autotuneStartHour: Int = 4
override fun aapsAutotune(daysBack: Int, autoSwitch: Boolean, profileToTune: String): String {
tunedProfile = null
updateButtonVisibility = View.GONE
lastRunSuccess = false
var logResult = ""
result = ""
if (profileFunction.getProfile() == null) {
result = rh.gs(R.string.profileswitch_ismissing)
return result
}
val detailedLog = sp.getBoolean(R.string.key_autotune_additional_log, false)
calculationRunning = true
lastNbDays = "" + daysBack
lastRun = dateUtil.now()
val profileStore = activePlugin.activeProfileSource.profile ?: return rh.gs(R.string.profileswitch_ismissing)
selectedProfile = if (profileToTune.isEmpty()) profileFunction.getProfileName() else profileToTune
profileFunction.getProfile()?.let { currentProfile ->
profile = profileStore.getSpecificProfile(profileToTune)?.let { ProfileSealed.Pure(it) } ?: currentProfile
}
val localInsulin = LocalInsulin("PumpInsulin", activePlugin.activeInsulin.peak, profile.dia) // var because localInsulin could be updated later with Tune Insulin peak/dia
log("Start Autotune with $daysBack days back")
autotuneFS.createAutotuneFolder() //create autotune subfolder for autotune files if not exists
autotuneFS.deleteAutotuneFiles() //clean autotune folder before run
// Today at 4 AM
var endTime = MidnightTime.calc(lastRun) + autotuneStartHour * 60 * 60 * 1000L
if (endTime > lastRun) endTime -= 24 * 60 * 60 * 1000L // Check if 4 AM is before now
val starttime = endTime - daysBack * 24 * 60 * 60 * 1000L
autotuneFS.exportSettings(settings(lastRun, daysBack, starttime, endTime))
tunedProfile = ATProfile(profile, localInsulin, injector).also {
it.profilename = rh.gs(R.string.autotune_tunedprofile_name)
}
pumpProfile = ATProfile(profile, localInsulin, injector).also {
it.profilename = selectedProfile
}
autotuneFS.exportPumpProfile(pumpProfile)
for (i in 0 until daysBack) {
val from = starttime + i * 24 * 60 * 60 * 1000L // get 24 hours BG values from 4 AM to 4 AM next day
val to = from + 24 * 60 * 60 * 1000L
log("Tune day " + (i + 1) + " of " + daysBack)
tunedProfile?.let { tunedProfile ->
autotuneIob.initializeData(from, to, tunedProfile) //autotuneIob contains BG and Treatments data from history (<=> query for ns-treatments and ns-entries)
autotuneFS.exportEntries(autotuneIob) //<=> ns-entries.yyyymmdd.json files exported for results compare with oref0 autotune on virtual machine
autotuneFS.exportTreatments(autotuneIob) //<=> ns-treatments.yyyymmdd.json files exported for results compare with oref0 autotune on virtual machine (include treatments ,tempBasal and extended
preppedGlucose = autotunePrep.categorize(tunedProfile) //<=> autotune.yyyymmdd.json files exported for results compare with oref0 autotune on virtual machine
}
if (preppedGlucose == null || tunedProfile == null) {
result = rh.gs(R.string.autotune_error)
log(result)
calculationRunning = false
rxBus.send(EventAutotuneUpdateGui())
tunedProfile = null
autotuneFS.exportResult(result)
autotuneFS.exportLogAndZip(lastRun)
return result
}
preppedGlucose?.let { preppedGlucose -> //preppedGlucose and tunedProfile should never be null here
autotuneFS.exportPreppedGlucose(preppedGlucose)
tunedProfile = autotuneCore.tuneAllTheThings(preppedGlucose, tunedProfile!!, pumpProfile)
}
// localInsulin = LocalInsulin("TunedInsulin", tunedProfile!!.peak, tunedProfile!!.dia) // Todo: Add tune Insulin option
autotuneFS.exportTunedProfile(tunedProfile!!) //<=> newprofile.yyyymmdd.json files exported for results compare with oref0 autotune on virtual machine
if (i < daysBack - 1) {
log("Partial result for day ${i + 1}".trimIndent())
result = rh.gs(R.string.autotune_partial_result, i + 1, daysBack)
rxBus.send(EventAutotuneUpdateGui())
}
logResult = showResults(tunedProfile, pumpProfile)
if (detailedLog)
autotuneFS.exportLog(lastRun, i + 1)
}
result = rh.gs(R.string.autotune_result, dateUtil.dateAndTimeString(lastRun))
if (!detailedLog)
autotuneFS.exportLog(lastRun)
autotuneFS.exportResult(logResult)
autotuneFS.zipAutotune(lastRun)
updateButtonVisibility = View.VISIBLE
if (autoSwitch) {
val circadian = sp.getBoolean(R.string.key_autotune_circadian_ic_isf, false)
tunedProfile?.let { tunedP ->
tunedP.profilename = pumpProfile.profilename
updateProfile(tunedP)
uel.log(
UserEntry.Action.STORE_PROFILE,
UserEntry.Sources.Autotune,
ValueWithUnit.SimpleString(tunedP.profilename)
)
updateButtonVisibility = View.GONE
tunedP.profileStore(circadian)?.let { profilestore ->
if (profileFunction.createProfileSwitch(
profilestore,
profileName = tunedP.profilename,
durationInMinutes = 0,
percentage = 100,
timeShiftInHours = 0,
timestamp = dateUtil.now()
)
) {
log("Profile Switch succeed ${tunedP.profilename}")
uel.log(
UserEntry.Action.PROFILE_SWITCH,
UserEntry.Sources.Autotune,
"Autotune AutoSwitch",
ValueWithUnit.SimpleString(tunedP.profilename))
}
rxBus.send(EventLocalProfileChanged())
}
}
}
lastRunSuccess = true
sp.putLong(R.string.key_autotune_last_run, lastRun)
rxBus.send(EventAutotuneUpdateGui())
calculationRunning = false
tunedProfile?.let {
return result
}
return "No Result" // should never occurs
}
private fun showResults(tunedProfile: ATProfile?, pumpProfile: ATProfile): String {
if (tunedProfile == null)
return "No Result" // should never occurs
val line = rh.gs(R.string.autotune_log_separator)
var strResult = line
strResult += rh.gs(R.string.autotune_log_title)
strResult += line
val tuneInsulin = sp.getBoolean(R.string.key_autotune_tune_insulin_curve, false)
if (tuneInsulin) {
strResult += rh.gs(R.string.autotune_log_peak, rh.gs(R.string.insulin_peak), pumpProfile.localInsulin.peak, tunedProfile.localInsulin.peak)
strResult += rh.gs(R.string.autotune_log_dia, rh.gs(R.string.ic_short), pumpProfile.localInsulin.dia, tunedProfile.localInsulin.dia)
}
// show ISF and CR
strResult += rh.gs(R.string.autotune_log_ic_isf, rh.gs(R.string.isf_short), pumpProfile.isf, tunedProfile.isf)
strResult += rh.gs(R.string.autotune_log_ic_isf, rh.gs(R.string.ic_short), pumpProfile.ic, tunedProfile.ic)
strResult += line
var totalBasal = 0.0
var totalTuned = 0.0
for (i in 0..23) {
totalBasal += pumpProfile.basal[i]
totalTuned += tunedProfile.basal[i]
val percentageChangeValue = tunedProfile.basal[i] / pumpProfile.basal[i] * 100 - 100
strResult += rh.gs(R.string.autotune_log_basal, i.toDouble(), pumpProfile.basal[i], tunedProfile.basal[i], tunedProfile.basalUntuned[i], percentageChangeValue)
}
strResult += line
strResult += rh.gs(R.string.autotune_log_sum_basal, totalBasal, totalTuned)
strResult += line
log(strResult)
return strResult
}
private fun settings(runDate: Long, nbDays: Int, firstloopstart: Long, lastloopend: Long): String {
var jsonString = ""
val jsonSettings = JSONObject()
val insulinInterface = activePlugin.activeInsulin
val utcOffset = T.msecs(TimeZone.getDefault().getOffset(dateUtil.now()).toLong()).hours()
val startDateString = dateUtil.toISOString(firstloopstart).substring(0,10)
val endDateString = dateUtil.toISOString(lastloopend - 24 * 60 * 60 * 1000L).substring(0,10)
val nsUrl = sp.getString(R.string.key_nsclientinternal_url, "")
val optCategorizeUam = if (sp.getBoolean(R.string.key_autotune_categorize_uam_as_basal, false)) "-c=true" else ""
val optInsulinCurve = if (sp.getBoolean(R.string.key_autotune_tune_insulin_curve, false)) "-i=true" else ""
try {
jsonSettings.put("datestring", dateUtil.toISOString(runDate))
jsonSettings.put("dateutc", dateUtil.toISOAsUTC(runDate))
jsonSettings.put("utcOffset", utcOffset)
jsonSettings.put("units", profileFunction.getUnits().asText)
jsonSettings.put("timezone", TimeZone.getDefault().id)
jsonSettings.put("url_nightscout", sp.getString(R.string.key_nsclientinternal_url, ""))
jsonSettings.put("nbdays", nbDays)
jsonSettings.put("startdate", startDateString)
jsonSettings.put("enddate", endDateString)
// command to change timezone
jsonSettings.put("timezone_command", "sudo ln -sf /usr/share/zoneinfo/" + TimeZone.getDefault().id + " /etc/localtime")
// oref0_command is for running oref0-autotune on a virtual machine in a dedicated ~/aaps subfolder
jsonSettings.put("oref0_command", "oref0-autotune -d=~/aaps -n=$nsUrl -s=$startDateString -e=$endDateString $optCategorizeUam $optInsulinCurve")
// aaps_command is for running modified oref0-autotune with exported data from aaps (ns-entries and ns-treatment json files copied in ~/aaps/autotune folder and pumpprofile.json copied in ~/aaps/settings/
jsonSettings.put("aaps_command", "aaps-autotune -d=~/aaps -s=$startDateString -e=$endDateString $optCategorizeUam $optInsulinCurve")
jsonSettings.put("categorize_uam_as_basal", sp.getBoolean(R.string.key_autotune_categorize_uam_as_basal, false))
jsonSettings.put("tune_insulin_curve", false)
val peaktime: Int = insulinInterface.peak
if (insulinInterface.id === Insulin.InsulinType.OREF_ULTRA_RAPID_ACTING)
jsonSettings.put("curve","ultra-rapid")
else if (insulinInterface.id === Insulin.InsulinType.OREF_RAPID_ACTING)
jsonSettings.put("curve", "rapid-acting")
else if (insulinInterface.id === Insulin.InsulinType.OREF_LYUMJEV) {
jsonSettings.put("curve", "ultra-rapid")
jsonSettings.put("useCustomPeakTime", true)
jsonSettings.put("insulinPeakTime", peaktime)
} else if (insulinInterface.id === Insulin.InsulinType.OREF_FREE_PEAK) {
jsonSettings.put("curve", if (peaktime > 55) "rapid-acting" else "ultra-rapid")
jsonSettings.put("useCustomPeakTime", true)
jsonSettings.put("insulinPeakTime", peaktime)
}
jsonString = jsonSettings.toString(4).replace("\\/", "/")
} catch (e: JSONException) { }
return jsonString
}
fun updateProfile(newProfile: ATProfile?) {
if (newProfile == null) return
val circadian = sp.getBoolean(R.string.key_autotune_circadian_ic_isf, false)
val profileStore = activePlugin.activeProfileSource.profile ?: ProfileStore(injector, JSONObject(), dateUtil)
val profileList: ArrayList<CharSequence> = profileStore.getProfileList()
var indexLocalProfile = -1
for (p in profileList.indices)
if (profileList[p] == newProfile.profilename)
indexLocalProfile = p
if (indexLocalProfile == -1) {
localProfilePlugin.addProfile(localProfilePlugin.copyFrom(newProfile.getProfile(circadian), newProfile.profilename))
return
}
localProfilePlugin.currentProfileIndex = indexLocalProfile
localProfilePlugin.currentProfile()?.dia = newProfile.dia
localProfilePlugin.currentProfile()?.basal = newProfile.basal()
localProfilePlugin.currentProfile()?.ic = newProfile.ic(circadian)
localProfilePlugin.currentProfile()?.isf = newProfile.isf(circadian)
localProfilePlugin.storeSettings()
}
private fun log(message: String) {
atLog("[Plugin] $message")
}
override fun specialEnableCondition(): Boolean = buildHelper.isEngineeringMode() && buildHelper.isDev()
override fun atLog(message: String) {
autotuneFS.atLog(message)
}
}

View file

@ -0,0 +1,536 @@
package info.nightscout.androidaps.plugins.general.autotune
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.LocalInsulin
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.plugins.general.autotune.data.*
import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.database.entities.Carbs
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.MidnightTime
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.T
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AutotunePrep @Inject constructor(
private val sp: SP,
private val dateUtil: DateUtil,
private val autotuneFS: AutotuneFS,
private val autotuneIob: AutotuneIob
) {
fun categorize(tunedprofile: ATProfile): PreppedGlucose? {
val preppedGlucose = categorizeBGDatums(tunedprofile, tunedprofile.localInsulin)
val tuneInsulin = sp.getBoolean(R.string.key_autotune_tune_insulin_curve, false)
if (tuneInsulin) {
var minDeviations = 1000000.0
val diaDeviations: MutableList<DiaDeviation> = ArrayList()
val peakDeviations: MutableList<PeakDeviation> = ArrayList()
val currentDIA = tunedprofile.localInsulin.dia
val currentPeak = tunedprofile.localInsulin.peak
var dia = currentDIA - 2
val endDIA = currentDIA + 2
while (dia <= endDIA)
{
var sqrtDeviations = 0.0
var deviations = 0.0
var deviationsSq = 0.0
val localInsulin = LocalInsulin("Ins_$currentPeak-$dia", currentPeak, dia)
val curve_output = categorizeBGDatums(tunedprofile, localInsulin, false)
val basalGlucose = curve_output?.basalGlucoseData
basalGlucose?.let {
for (hour in 0..23) {
for (i in 0..(basalGlucose.size-1)) {
val myHour = ((basalGlucose[i].date - MidnightTime.calc(basalGlucose[i].date)) / T.hours(1).msecs()).toInt()
if (hour == myHour) {
sqrtDeviations += Math.pow(Math.abs(basalGlucose[i].deviation), 0.5)
deviations += Math.abs(basalGlucose[i].deviation)
deviationsSq += Math.pow(basalGlucose[i].deviation, 2.0)
}
}
}
val meanDeviation = Round.roundTo(Math.abs(deviations / basalGlucose.size), 0.001)
val smrDeviation = Round.roundTo(Math.pow(sqrtDeviations / basalGlucose.size, 2.0), 0.001)
val rmsDeviation = Round.roundTo(Math.pow(deviationsSq / basalGlucose.size, 0.5), 0.001)
log("insulinEndTime $dia meanDeviation: $meanDeviation SMRDeviation: $smrDeviation RMSDeviation: $rmsDeviation (mg/dL)")
diaDeviations.add(
DiaDeviation(
dia = dia,
meanDeviation = meanDeviation,
smrDeviation = smrDeviation,
rmsDeviation = rmsDeviation
)
)
}
preppedGlucose?.diaDeviations = diaDeviations
deviations = Round.roundTo(deviations, 0.001)
if (deviations < minDeviations)
minDeviations = Round.roundTo(deviations, 0.001)
dia += 1.0
}
// consoleError('Optimum insulinEndTime', newDIA, 'mean deviation:', JSMath.Round(minDeviations/basalGlucose.length*1000)/1000, '(mg/dL)');
//consoleError(diaDeviations);
minDeviations = 1000000.0
var peak = currentPeak - 10
val endPeak = currentPeak + 10
while (peak <= endPeak)
{
var sqrtDeviations = 0.0
var deviations = 0.0
var deviationsSq = 0.0
val localInsulin = LocalInsulin("Ins_$peak-$currentDIA", peak, currentDIA)
val curve_output = categorizeBGDatums(tunedprofile, localInsulin, false)
val basalGlucose = curve_output?.basalGlucoseData
basalGlucose?.let {
for (hour in 0..23) {
for (i in 0..(basalGlucose.size - 1)) {
val myHour = ((basalGlucose[i].date - MidnightTime.calc(basalGlucose[i].date)) / T.hours(1).msecs()).toInt()
if (hour == myHour) {
//console.error(basalGlucose[i].deviation);
sqrtDeviations += Math.pow(Math.abs(basalGlucose[i].deviation), 0.5)
deviations += Math.abs(basalGlucose[i].deviation)
deviationsSq += Math.pow(basalGlucose[i].deviation, 2.0)
}
}
}
val meanDeviation = Round.roundTo(deviations / basalGlucose.size, 0.001)
val smrDeviation = Round.roundTo(Math.pow(sqrtDeviations / basalGlucose.size, 2.0), 0.001)
val rmsDeviation = Round.roundTo(Math.pow(deviationsSq / basalGlucose.size, 0.5), 0.001)
log("insulinPeakTime $peak meanDeviation: $meanDeviation SMRDeviation: $smrDeviation RMSDeviation: $rmsDeviation (mg/dL)")
peakDeviations.add(
PeakDeviation
(
peak = peak,
meanDeviation = meanDeviation,
smrDeviation = smrDeviation,
rmsDeviation = rmsDeviation,
)
)
}
deviations = Round.roundTo(deviations, 0.001);
if (deviations < minDeviations)
minDeviations = Round.roundTo(deviations, 0.001)
peak += 5
}
//consoleError($"Optimum insulinPeakTime {newPeak} mean deviation: {JSMath.Round(minDeviations/basalGlucose.Count, 3)} (mg/dL)");
//consoleError(peakDeviations);
preppedGlucose?.peakDeviations = peakDeviations
}
return preppedGlucose
}
// private static Logger log = LoggerFactory.getLogger(AutotunePlugin.class);
fun categorizeBGDatums(tunedprofile: ATProfile, localInsulin: LocalInsulin, verbose: Boolean = true): PreppedGlucose? {
//lib/meals is called before to get only meals data (in AAPS it's done in AutotuneIob)
val treatments: MutableList<Carbs> = autotuneIob.meals
val boluses: MutableList<Bolus> = autotuneIob.boluses
// Bloc between #21 and # 54 replaced by bloc below (just remove BG value below 39, Collections.sort probably not necessary because BG values already sorted...)
val glucose = autotuneIob.glucose
val glucoseData: MutableList<GlucoseValue> = ArrayList()
for (i in glucose.indices) {
if (glucose[i].value > 39) {
glucoseData.add(glucose[i])
}
}
if (glucose.size == 0 || glucoseData.size == 0 ) {
if (verbose)
log("No BG value received")
return null
}
glucoseData.sortWith(object: Comparator<GlucoseValue>{ override fun compare(o1: GlucoseValue, o2: GlucoseValue): Int = (o2.timestamp - o1.timestamp).toInt() })
// Bloc below replace bloc between #55 and #71
// boluses and maxCarbs not used here ?,
// IOBInputs are for iob calculation (done here in AutotuneIob Class)
//val boluses = 0
//val maxCarbs = 0
if (treatments.size == 0) {
if (verbose)
log("No Carbs entries")
//return null
}
if (autotuneIob.boluses.size == 0) {
if (verbose)
log("No treatment received")
return null
}
var csfGlucoseData: MutableList<BGDatum> = ArrayList()
var isfGlucoseData: MutableList<BGDatum> = ArrayList()
var basalGlucoseData: MutableList<BGDatum> = ArrayList()
val uamGlucoseData: MutableList<BGDatum> = ArrayList()
val crData: MutableList<CRDatum> = ArrayList()
//Bloc below replace bloc between #72 and #93
// I keep it because BG lines in log are consistent between AAPS and Oref0
val bucketedData: MutableList<BGDatum> = ArrayList()
bucketedData.add(BGDatum(glucoseData[0], dateUtil))
//int j=0;
var k = 0 // index of first value used by bucket
//for loop to validate and bucket the data
for (i in 1 until glucoseData.size) {
val BGTime = glucoseData[i].timestamp
val lastBGTime = glucoseData[k].timestamp
val elapsedMinutes = (BGTime - lastBGTime) / (60 * 1000)
if (Math.abs(elapsedMinutes) >= 2) {
//j++; // move to next bucket
k = i // store index of first value used by bucket
bucketedData.add(BGDatum(glucoseData[i], dateUtil))
} else {
// average all readings within time deadband
val average = glucoseData[k]
for (l in k + 1 until i + 1) {
average.value += glucoseData[l].value
}
average.value = average.value / (i - k + 1)
bucketedData.add(BGDatum(average, dateUtil))
}
}
// Here treatments contains only meals data
// bloc between #94 and #114 remove meals before first BG value
// Bloc below replace bloc between #115 and #122 (initialize data before main loop)
// crInitialxx are declaration to be able to use these data in whole loop
var calculatingCR = false
var absorbing = false
var uam = false // unannounced meal
var mealCOB = 0.0
var mealCarbs = 0.0
var crCarbs = 0.0
var type = ""
var crInitialIOB = 0.0
var crInitialBG = 0.0
var crInitialCarbTime = 0L
//categorize.js#123 (Note: don't need fullHistory because data are managed in AutotuneIob Class)
//Here is main loop between #125 and #366
// main for loop
for (i in bucketedData.size - 5 downTo 1) {
val glucoseDatum = bucketedData[i]
//log.debug(glucoseDatum);
val BGTime = glucoseDatum.date
// As we're processing each data point, go through the treatment.carbs and see if any of them are older than
// the current BG data point. If so, add those carbs to COB.
val treatment = if (treatments.size > 0) treatments[treatments.size - 1] else null
var myCarbs = 0.0
if (treatment != null) {
if (treatment.timestamp < BGTime) {
if (treatment.amount > 0.0) {
mealCOB += treatment.amount
mealCarbs += treatment.amount
myCarbs = treatment.amount
}
treatments.removeAt(treatments.size - 1)
}
}
var bg = 0.0
var avgDelta = 0.0
// TODO: re-implement interpolation to avoid issues here with gaps
// calculate avgDelta as last 4 datapoints to better catch more rises after COB hits zero
if (bucketedData[i].value != 0.0 && bucketedData[i + 4].value != 0.0) {
//log.debug(bucketedData[i]);
bg = bucketedData[i].value
if (bg < 40 || bucketedData[i + 4].value < 40) {
//process.stderr.write("!");
continue
}
avgDelta = (bg - bucketedData[i + 4].value) / 4
} else {
if (verbose)
log("Could not find glucose data")
}
avgDelta = Round.roundTo(avgDelta, 0.01)
glucoseDatum.avgDelta = avgDelta
//sens = ISF
val sens = tunedprofile.isf
// for IOB calculations, use the average of the last 4 hours' basals to help convergence;
// this helps since the basal this hour could be different from previous, especially if with autotune they start to diverge.
// use the pumpbasalprofile to properly calculate IOB during periods where no temp basal is set
/* Note Philoul currentPumpBasal never used in oref0 Autotune code
var currentPumpBasal = pumpprofile.profile.getBasal(BGTime)
currentPumpBasal += pumpprofile.profile.getBasal(BGTime - 1 * 60 * 60 * 1000)
currentPumpBasal += pumpprofile.profile.getBasal(BGTime - 2 * 60 * 60 * 1000)
currentPumpBasal += pumpprofile.profile.getBasal(BGTime - 3 * 60 * 60 * 1000)
currentPumpBasal = Round.roundTo(currentPumpBasal / 4, 0.001) //CurrentPumpBasal for iob calculation is average of 4 last pumpProfile Basal rate
*/
// this is the current autotuned basal, used for everything else besides IOB calculations
val currentBasal = tunedprofile.getBasal(BGTime)
// basalBGI is BGI of basal insulin activity.
val basalBGI = Round.roundTo(currentBasal * sens / 60 * 5, 0.01) // U/hr * mg/dL/U * 1 hr / 60 minutes * 5 = mg/dL/5m
//console.log(JSON.stringify(IOBInputs.profile));
// call iob since calculated elsewhere
//var iob = getIOB(IOBInputs)[0];
// in autotune iob is calculated with 6 hours of history data, tunedProfile and average pumpProfile basal rate...
//log("currentBasal: " + currentBasal + " BGTime: " + BGTime + " / " + dateUtil!!.timeStringWithSeconds(BGTime) + "******************************************************************************************")
val iob = autotuneIob.getIOB(BGTime, localInsulin) // add localInsulin to be independent to InsulinPlugin
// activity times ISF times 5 minutes is BGI
val BGI = Round.roundTo(-iob.activity * sens * 5, 0.01)
// datum = one glucose data point (being prepped to store in output)
glucoseDatum.bgi = BGI
// calculating deviation
var deviation = avgDelta - BGI
// set positive deviations to zero if BG is below 80
if (bg < 80 && deviation > 0) {
deviation = 0.0
}
// rounding and storing deviation
deviation = Round.roundTo(deviation, 0.01)
glucoseDatum.deviation = deviation
// Then, calculate carb absorption for that 5m interval using the deviation.
if (mealCOB > 0) {
val ci = Math.max(deviation, sp.getDouble("openapsama_min_5m_carbimpact", 3.0))
val absorbed = ci * tunedprofile.ic / sens
// Store the COB, and use it as the starting point for the next data point.
mealCOB = Math.max(0.0, mealCOB - absorbed)
}
// Calculate carb ratio (CR) independently of CSF and ISF
// Use the time period from meal bolus/carbs until COB is zero and IOB is < currentBasal/2
// For now, if another meal IOB/COB stacks on top of it, consider them together
// Compare beginning and ending BGs, and calculate how much more/less insulin is needed to neutralize
// Use entered carbs vs. starting IOB + delivered insulin + needed-at-end insulin to directly calculate CR.
if (mealCOB > 0 || calculatingCR) {
// set initial values when we first see COB
crCarbs += myCarbs
if (calculatingCR == false) {
crInitialIOB = iob.iob
crInitialBG = glucoseDatum.value
crInitialCarbTime = glucoseDatum.date
if (verbose)
log("CRInitialIOB: " + crInitialIOB + " CRInitialBG: " + crInitialBG + " CRInitialCarbTime: " + dateUtil.toISOString(crInitialCarbTime))
}
// keep calculatingCR as long as we have COB or enough IOB
if (mealCOB > 0 && i > 1) {
calculatingCR = true
} else if (iob.iob > currentBasal / 2 && i > 1) {
calculatingCR = true
// when COB=0 and IOB drops low enough, record end values and be done calculatingCR
} else {
val crEndIOB = iob.iob
val crEndBG = glucoseDatum.value
val crEndTime = glucoseDatum.date
if (verbose)
log("CREndIOB: " + crEndIOB + " CREndBG: " + crEndBG + " CREndTime: " + dateUtil.toISOString(crEndTime))
val crDatum = CRDatum(dateUtil)
crDatum.crInitialBG = crInitialBG
crDatum.crInitialIOB = crInitialIOB
crDatum.crInitialCarbTime = crInitialCarbTime
crDatum.crEndBG = crEndBG
crDatum.crEndIOB = crEndIOB
crDatum.crEndTime = crEndTime
crDatum.crCarbs = crCarbs
//log.debug(CRDatum);
//String crDataString = "{\"CRInitialIOB\": " + CRInitialIOB + ", \"CRInitialBG\": " + CRInitialBG + ", \"CRInitialCarbTime\": " + CRInitialCarbTime + ", \"CREndIOB\": " + CREndIOB + ", \"CREndBG\": " + CREndBG + ", \"CREndTime\": " + CREndTime + ", \"CRCarbs\": " + CRCarbs + "}";
val CRElapsedMinutes = Math.round((crEndTime - crInitialCarbTime) / (1000 * 60).toFloat())
//log.debug(CREndTime - CRInitialCarbTime, CRElapsedMinutes);
if (CRElapsedMinutes < 60 || i == 1 && mealCOB > 0) {
if (verbose)
log("Ignoring $CRElapsedMinutes m CR period.")
} else {
crData.add(crDatum)
}
crCarbs = 0.0
calculatingCR = false
}
}
// If mealCOB is zero but all deviations since hitting COB=0 are positive, assign those data points to CSFGlucoseData
// Once deviations go negative for at least one data point after COB=0, we can use the rest of the data to tune ISF or basals
if (mealCOB > 0 || absorbing || mealCarbs > 0) {
// if meal IOB has decayed, then end absorption after this data point unless COB > 0
absorbing = if (iob.iob < currentBasal / 2) {
false
// otherwise, as long as deviations are positive, keep tracking carb deviations
} else if (deviation > 0) {
true
} else {
false
}
if (!absorbing && mealCOB == 0.0) {
mealCarbs = 0.0
}
// check previous "type" value, and if it wasn't csf, set a mealAbsorption start flag
//log.debug(type);
if (type != "csf") {
glucoseDatum.mealAbsorption = "start"
if (verbose)
log(glucoseDatum.mealAbsorption + " carb absorption")
}
type = "csf"
glucoseDatum.mealCarbs = mealCarbs.toInt()
//if (i == 0) { glucoseDatum.mealAbsorption = "end"; }
csfGlucoseData.add(glucoseDatum)
} else {
// check previous "type" value, and if it was csf, set a mealAbsorption end flag
if (type == "csf") {
csfGlucoseData[csfGlucoseData.size - 1].mealAbsorption = "end"
if (verbose)
log(csfGlucoseData[csfGlucoseData.size - 1].mealAbsorption + " carb absorption")
}
if (iob.iob > 2 * currentBasal || deviation > 6 || uam) {
uam = if (deviation > 0) {
true
} else {
false
}
if (type != "uam") {
glucoseDatum.uamAbsorption = "start"
if (verbose)
log(glucoseDatum.uamAbsorption + " unannnounced meal absorption")
}
type = "uam"
uamGlucoseData.add(glucoseDatum)
} else {
if (type == "uam") {
if (verbose)
log("end unannounced meal absorption")
}
// Go through the remaining time periods and divide them into periods where scheduled basal insulin activity dominates. This would be determined by calculating the BG impact of scheduled basal insulin
// (for example 1U/hr * 48 mg/dL/U ISF = 48 mg/dL/hr = 5 mg/dL/5m), and comparing that to BGI from bolus and net basal insulin activity.
// When BGI is positive (insulin activity is negative), we want to use that data to tune basals
// When BGI is smaller than about 1/4 of basalBGI, we want to use that data to tune basals
// When BGI is negative and more than about 1/4 of basalBGI, we can use that data to tune ISF,
// unless avgDelta is positive: then that's some sort of unexplained rise we don't want to use for ISF, so that means basals
if (basalBGI > -4 * BGI) {
type = "basal"
basalGlucoseData.add(glucoseDatum)
} else {
if (avgDelta > 0 && avgDelta > -2 * BGI) {
//type="unknown"
type = "basal"
basalGlucoseData.add(glucoseDatum)
} else {
type = "ISF"
isfGlucoseData.add(glucoseDatum)
}
}
}
}
// debug line to print out all the things
if (verbose)
log((if (absorbing) 1 else 0).toString() + " mealCOB: " + Round.roundTo(mealCOB, 0.1) + " mealCarbs: " + Math.round(mealCarbs) + " basalBGI: " + Round.roundTo(basalBGI, 0.1) + " BGI: " + Round.roundTo(BGI, 0.1) + " IOB: " + iob.iob+ " Activity: " + iob.activity + " at " + dateUtil.timeStringWithSeconds(BGTime) + " dev: " + deviation + " avgDelta: " + avgDelta + " " + type)
}
//****************************************************************************************************************************************
// categorize.js Lines 372-383
for (crDatum in crData) {
crDatum.crInsulin = dosed(crDatum.crInitialCarbTime, crDatum.crEndTime, boluses)
}
// categorize.js Lines 384-436
val CSFLength = csfGlucoseData.size
var ISFLength = isfGlucoseData.size
val UAMLength = uamGlucoseData.size
var basalLength = basalGlucoseData.size
if (sp.getBoolean(R.string.key_autotune_categorize_uam_as_basal, false)) {
if (verbose)
log("Categorizing all UAM data as basal.")
basalGlucoseData.addAll(uamGlucoseData)
} else if (CSFLength > 12) {
if (verbose)
log("Found at least 1h of carb: assuming meals were announced, and categorizing UAM data as basal.")
basalGlucoseData.addAll(uamGlucoseData)
} else {
if (2 * basalLength < UAMLength) {
//log.debug(basalGlucoseData, UAMGlucoseData);
if (verbose) {
log("Warning: too many deviations categorized as UnAnnounced Meals")
log("Adding $UAMLength UAM deviations to $basalLength basal ones")
}
basalGlucoseData.addAll(uamGlucoseData)
//log.debug(basalGlucoseData);
// if too much data is excluded as UAM, add in the UAM deviations, but then discard the highest 50%
basalGlucoseData.sortWith(object: Comparator<BGDatum>{ override fun compare(o1: BGDatum, o2: BGDatum): Int = (100 * o1.deviation - 100 * o2.deviation).toInt() }) //deviation rouded to 0.01, so *100 to avoid crash during sort
val newBasalGlucose: MutableList<BGDatum> = ArrayList()
for (i in 0 until basalGlucoseData.size / 2) {
newBasalGlucose.add(basalGlucoseData[i])
}
//log.debug(newBasalGlucose);
basalGlucoseData = newBasalGlucose
if (verbose)
log("and selecting the lowest 50%, leaving " + basalGlucoseData.size + " basal+UAM ones")
}
if (2 * ISFLength < UAMLength) {
if (verbose)
log("Adding $UAMLength UAM deviations to $ISFLength ISF ones")
isfGlucoseData.addAll(uamGlucoseData)
// if too much data is excluded as UAM, add in the UAM deviations to ISF, but then discard the highest 50%
isfGlucoseData.sortWith(object: Comparator<BGDatum>{ override fun compare(o1: BGDatum, o2: BGDatum): Int = (100 * o1.deviation - 100 * o2.deviation).toInt() }) //deviation rouded to 0.01, so *100 to avoid crash during sort
val newISFGlucose: MutableList<BGDatum> = ArrayList()
for (i in 0 until isfGlucoseData.size / 2) {
newISFGlucose.add(isfGlucoseData[i])
}
//console.error(newISFGlucose);
isfGlucoseData = newISFGlucose
if (verbose)
log("and selecting the lowest 50%, leaving " + isfGlucoseData.size + " ISF+UAM ones")
//log.error(ISFGlucoseData.length, UAMLength);
}
}
basalLength = basalGlucoseData.size
ISFLength = isfGlucoseData.size
if (4 * basalLength + ISFLength < CSFLength && ISFLength < 10) {
if (verbose) {
log("Warning: too many deviations categorized as meals")
//log.debug("Adding",CSFLength,"CSF deviations to",basalLength,"basal ones");
//var basalGlucoseData = basalGlucoseData.concat(CSFGlucoseData);
log("Adding $CSFLength CSF deviations to $ISFLength ISF ones")
}
isfGlucoseData.addAll(csfGlucoseData)
csfGlucoseData = ArrayList()
}
// categorize.js Lines 437-444
if (verbose)
log("CRData: " + crData.size + " CSFGlucoseData: " + csfGlucoseData.size + " ISFGlucoseData: " + isfGlucoseData.size + " BasalGlucoseData: " + basalGlucoseData.size)
return PreppedGlucose(autotuneIob.startBG, crData, csfGlucoseData, isfGlucoseData, basalGlucoseData, dateUtil)
}
//dosed.js full
private fun dosed(start: Long, end: Long, treatments: List<Bolus>): Double {
var insulinDosed = 0.0
if (treatments.size == 0) {
log("No treatments to process.")
return 0.0
}
for (treatment in treatments) {
if (treatment.amount != 0.0 && treatment.timestamp > start && treatment.timestamp <= end) {
insulinDosed += treatment.amount
//log("CRDATA;${dateUtil.toISOString(start)};${dateUtil.toISOString(end)};${treatment.timestamp};${treatment.amount};$insulinDosed")
}
}
//log("insulin dosed: " + insulinDosed);
return Round.roundTo(insulinDosed, 0.001)
}
private fun log(message: String) {
autotuneFS.atLog("[Prep] $message")
}
}

View file

@ -0,0 +1,250 @@
package info.nightscout.androidaps.plugins.general.autotune.data
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.data.LocalInsulin
import info.nightscout.androidaps.data.ProfileSealed
import info.nightscout.androidaps.data.PureProfile
import info.nightscout.androidaps.database.data.Block
import info.nightscout.androidaps.extensions.blockValueBySeconds
import info.nightscout.androidaps.extensions.pureProfileFromJson
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.T
import info.nightscout.shared.SafeParse
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.text.DecimalFormat
import java.util.*
import javax.inject.Inject
class ATProfile(profile: Profile, var localInsulin: LocalInsulin, val injector: HasAndroidInjector) {
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var sp: SP
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var config: Config
@Inject lateinit var rxBus: RxBus
@Inject lateinit var rh: ResourceHelper
var profile: ProfileSealed
var circadianProfile: ProfileSealed
lateinit var pumpProfile: ProfileSealed
var profilename: String = ""
var basal = DoubleArray(24)
var basalUntuned = IntArray(24)
var ic = 0.0
var isf = 0.0
var dia = 0.0
var peak = 0
var isValid: Boolean = false
var from: Long = 0
var pumpProfileAvgISF = 0.0
var pumpProfileAvgIC = 0.0
val icSize: Int
get() = profile.getIcsValues().size
val isfSize: Int
get() = profile.getIsfsMgdlValues().size
val avgISF: Double
get() = if (profile.getIsfsMgdlValues().size == 1) profile.getIsfsMgdlValues().get(0).value else Round.roundTo(averageProfileValue(profile.getIsfsMgdlValues()), 0.01)
val avgIC: Double
get() = if (profile.getIcsValues().size == 1) profile.getIcsValues().get(0).value else Round.roundTo(averageProfileValue(profile.getIcsValues()), 0.01)
fun getBasal(timestamp: Long): Double = basal[Profile.secondsFromMidnight(timestamp)/3600]
// for localProfilePlugin Synchronisation
fun basal() = jsonArray(basal)
fun ic(circadian: Boolean = false): JSONArray {
if(circadian)
return jsonArray(pumpProfile.icBlocks, avgIC/pumpProfileAvgIC)
return jsonArray(ic)
}
fun isf(circadian: Boolean = false): JSONArray {
if(circadian)
return jsonArray(pumpProfile.isfBlocks, avgISF/pumpProfileAvgISF)
return jsonArray(Profile.fromMgdlToUnits(isf, profile.units))
}
fun getProfile(circadian: Boolean = false): PureProfile {
return if (circadian)
circadianProfile.convertToNonCustomizedProfile(dateUtil)
else
profile.convertToNonCustomizedProfile(dateUtil)
}
fun updateProfile() {
data()?.let { profile = ProfileSealed.Pure(it) }
data(true)?.let { circadianProfile = ProfileSealed.Pure(it) }
}
//Export json string with oref0 format used for autotune
// Include min_5m_carbimpact, insulin type, single value for carb_ratio and isf
fun profiletoOrefJSON(): String {
var jsonString = ""
val json = JSONObject()
val insulinInterface: Insulin = activePlugin.activeInsulin
try {
json.put("name", profilename)
json.put("min_5m_carbimpact", sp.getDouble("openapsama_min_5m_carbimpact", 3.0))
json.put("dia", dia)
if (insulinInterface.id === Insulin.InsulinType.OREF_ULTRA_RAPID_ACTING) json.put(
"curve",
"ultra-rapid"
) else if (insulinInterface.id === Insulin.InsulinType.OREF_RAPID_ACTING) json.put("curve", "rapid-acting") else if (insulinInterface.id === Insulin.InsulinType.OREF_LYUMJEV) {
json.put("curve", "ultra-rapid")
json.put("useCustomPeakTime", true)
json.put("insulinPeakTime", 45)
} else if (insulinInterface.id === Insulin.InsulinType.OREF_FREE_PEAK) {
val peaktime: Int = sp.getInt(rh.gs(R.string.key_insulin_oref_peak), 75)
json.put("curve", if (peaktime > 50) "rapid-acting" else "ultra-rapid")
json.put("useCustomPeakTime", true)
json.put("insulinPeakTime", peaktime)
}
val basals = JSONArray()
for (h in 0..23) {
val secondfrommidnight = h * 60 * 60
var time: String
time = DecimalFormat("00").format(h) + ":00:00"
basals.put(
JSONObject()
.put("start", time)
.put("minutes", h * 60)
.put("rate", profile.getBasalTimeFromMidnight(secondfrommidnight)
)
)
}
json.put("basalprofile", basals)
val isfvalue = Round.roundTo(avgISF, 0.001)
json.put(
"isfProfile",
JSONObject().put(
"sensitivities",
JSONArray().put(JSONObject().put("i", 0).put("start", "00:00:00").put("sensitivity", isfvalue).put("offset", 0).put("x", 0).put("endoffset", 1440))
)
)
json.put("carb_ratio", avgIC)
json.put("autosens_max", SafeParse.stringToDouble(sp.getString(R.string.key_openapsama_autosens_max, "1.2")))
json.put("autosens_min", SafeParse.stringToDouble(sp.getString(R.string.key_openapsama_autosens_min, "0.7")))
json.put("units", GlucoseUnit.MGDL.asText)
json.put("timezone", TimeZone.getDefault().id)
jsonString = json.toString(2).replace("\\/", "/")
} catch (e: JSONException) {}
return jsonString
}
fun data(circadian: Boolean = false): PureProfile? {
val json: JSONObject = profile.toPureNsJson(dateUtil)
try {
json.put("dia", dia)
if (circadian) {
json.put("sens", jsonArray(pumpProfile.isfBlocks, avgISF/pumpProfileAvgISF))
json.put("carbratio", jsonArray(pumpProfile.icBlocks, avgIC/pumpProfileAvgIC))
} else {
json.put("sens", jsonArray(Profile.fromMgdlToUnits(isf, profile.units)))
json.put("carbratio", jsonArray(ic))
}
json.put("basal", jsonArray(basal))
} catch (e: JSONException) {
}
return pureProfileFromJson(json, dateUtil, profile.units.asText)
}
fun profileStore(circadian: Boolean = false): ProfileStore?
{
var profileStore: ProfileStore? = null
val json = JSONObject()
val store = JSONObject()
val tunedProfile = if (circadian) circadianProfile else profile
if (profilename.isEmpty())
profilename = rh.gs(R.string.autotune_tunedprofile_name)
try {
store.put(profilename, tunedProfile.toPureNsJson(dateUtil))
json.put("defaultProfile", profilename)
json.put("store", store)
json.put("startDate", dateUtil.toISOAsUTC(dateUtil.now()))
profileStore = ProfileStore(injector, json, dateUtil)
} catch (e: JSONException) { }
return profileStore
}
fun jsonArray(values: DoubleArray): JSONArray {
val json = JSONArray()
for (h in 0..23) {
val secondfrommidnight = h * 60 * 60
val df = DecimalFormat("00")
val time = df.format(h.toLong()) + ":00"
json.put(
JSONObject()
.put("time", time)
.put("timeAsSeconds", secondfrommidnight)
.put("value", values[h])
)
}
return json
}
fun jsonArray(value: Double) =
JSONArray().put(
JSONObject()
.put("time", "00:00")
.put("timeAsSeconds", 0)
.put("value", value)
)
fun jsonArray(values: List<Block>, multiplier: Double = 1.0): JSONArray {
val json = JSONArray()
var elapsedHours = 0L
values.forEach {
val value = values.blockValueBySeconds(T.hours(elapsedHours).secs().toInt(), multiplier, 0)
json.put(
JSONObject()
.put("time", DecimalFormat("00").format(elapsedHours) + ":00")
.put("timeAsSeconds", T.hours(elapsedHours).secs())
.put("value", value)
)
elapsedHours += T.msecs(it.duration).hours()
}
return json
}
companion object {
fun averageProfileValue(pf: Array<Profile.ProfileValue>?): Double {
var avgValue = 0.0
val secondPerDay = 24 * 60 * 60
if (pf == null) return avgValue
for (i in pf.indices) {
avgValue += pf[i].value * ((if (i == pf.size - 1) secondPerDay else pf[i + 1].timeAsSeconds) - pf[i].timeAsSeconds)
}
avgValue /= secondPerDay.toDouble()
return avgValue
}
}
init {
injector.androidInjector().inject(this)
this.profile = profile as ProfileSealed
circadianProfile = profile
isValid = profile.isValid
if (isValid) {
//initialize tuned value with current profile values
for (h in 0..23) {
basal[h] = Round.roundTo(profile.basalBlocks.blockValueBySeconds(T.hours(h.toLong()).secs().toInt(), 1.0, 0), 0.001)
}
ic = avgIC
isf = avgISF
pumpProfile = profile
pumpProfileAvgIC = avgIC
pumpProfileAvgISF = avgISF
}
dia = localInsulin.dia
peak = localInsulin.peak
}
}

View file

@ -0,0 +1,80 @@
package info.nightscout.androidaps.plugins.general.autotune.data
import info.nightscout.androidaps.database.entities.GlucoseValue.TrendArrow
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.T
import org.json.JSONException
import org.json.JSONObject
import java.util.*
/**
* Created by Rumen Georgiev on 2/24/2018.
*/
class BGDatum {
//Added by Rumen for autotune
var id: Long = 0
var date = 0L
var value = 0.0
var direction: TrendArrow? = null
var deviation = 0.0
var bgi = 0.0
var mealAbsorption = ""
var mealCarbs = 0
var uamAbsorption = ""
var avgDelta = 0.0
var bgReading: GlucoseValue? = null
private set
var dateUtil: DateUtil
constructor(dateUtil: DateUtil) { this.dateUtil = dateUtil}
constructor(json: JSONObject, dateUtil: DateUtil) {
this.dateUtil = dateUtil
try {
if (json.has("_id")) id = json.getLong("_id")
if (json.has("date")) date = json.getLong("date")
if (json.has("sgv")) value = json.getDouble("sgv")
if (json.has("direction")) direction = TrendArrow.fromString(json.getString("direction"))
if (json.has("deviation")) deviation = json.getDouble("deviation")
if (json.has("BGI")) bgi = json.getDouble("BGI")
if (json.has("avgDelta")) avgDelta = json.getDouble("avgDelta")
if (json.has("mealAbsorption")) mealAbsorption = json.getString("mealAbsorption")
if (json.has("mealCarbs")) mealCarbs = json.getInt("mealCarbs")
} catch (e: JSONException) {
}
}
constructor(glucoseValue: GlucoseValue, dateUtil: DateUtil) {
this.dateUtil = dateUtil
date = glucoseValue.timestamp
value = glucoseValue.value
direction = glucoseValue.trendArrow
id = glucoseValue.id
this.bgReading = glucoseValue
}
fun toJSON(mealData: Boolean): JSONObject {
val bgjson = JSONObject()
val utcOffset = T.msecs(TimeZone.getDefault().getOffset(dateUtil.now()).toLong()).hours()
try {
bgjson.put("_id", id)
bgjson.put("date", date)
bgjson.put("dateString", dateUtil.toISOAsUTC(date))
bgjson.put("sgv", value)
bgjson.put("direction", direction)
bgjson.put("type", "sgv")
bgjson.put("sysTime", dateUtil.toISOAsUTC(date))
bgjson.put("utcOffset", utcOffset)
bgjson.put("glucose", value)
bgjson.put("avgDelta", avgDelta)
bgjson.put("BGI", bgi)
bgjson.put("deviation", deviation)
if (mealData) {
bgjson.put("mealAbsorption", mealAbsorption)
bgjson.put("mealCarbs", mealCarbs)
}
} catch (e: JSONException) {
}
return bgjson
}
}

View file

@ -0,0 +1,54 @@
package info.nightscout.androidaps.plugins.general.autotune.data
import info.nightscout.androidaps.utils.DateUtil
import org.json.JSONException
import org.json.JSONObject
/**
* Created by Rumen Georgiev on 2/26/2018.
*/
class CRDatum {
var crInitialIOB = 0.0
var crInitialBG = 0.0
var crInitialCarbTime = 0L
var crEndIOB = 0.0
var crEndBG = 0.0
var crEndTime = 0L
var crCarbs = 0.0
var crInsulin = 0.0
var crInsulinTotal = 0.0
var dateUtil: DateUtil
constructor(dateUtil: DateUtil) { this.dateUtil = dateUtil}
constructor(json: JSONObject, dateUtil: DateUtil) {
this.dateUtil = dateUtil
try {
if (json.has("CRInitialIOB")) crInitialIOB = json.getDouble("CRInitialIOB")
if (json.has("CRInitialBG")) crInitialBG = json.getDouble("CRInitialBG")
if (json.has("CRInitialCarbTime")) crInitialCarbTime = dateUtil.fromISODateString(json.getString("CRInitialCarbTime"))
if (json.has("CREndIOB")) crEndIOB = json.getDouble("CREndIOB")
if (json.has("CREndBG")) crEndBG = json.getDouble("CREndBG")
if (json.has("CREndTime")) crEndTime = dateUtil.fromISODateString(json.getString("CREndTime"))
if (json.has("CRCarbs")) crCarbs = json.getDouble("CRCarbs")
if (json.has("CRInsulin")) crInsulin = json.getDouble("CRInsulin")
} catch (e: JSONException) {
}
}
fun toJSON(): JSONObject {
val crjson = JSONObject()
try {
crjson.put("CRInitialIOB", crInitialIOB)
crjson.put("CRInitialBG", crInitialBG.toInt())
crjson.put("CRInitialCarbTime", dateUtil.toISOString(crInitialCarbTime))
crjson.put("CREndIOB", crEndIOB)
crjson.put("CREndBG", crEndBG.toInt())
crjson.put("CREndTime", dateUtil.toISOString(crEndTime))
crjson.put("CRCarbs", crCarbs.toInt())
crjson.put("CRInsulin", crInsulin)
} catch (e: JSONException) {
}
return crjson
}
}

View file

@ -0,0 +1,29 @@
package info.nightscout.androidaps.plugins.general.autotune.data
import org.json.JSONException
import org.json.JSONObject
class DiaDeviation(var dia: Double = 0.0, var meanDeviation: Double = 0.0, var smrDeviation: Double = 0.0, var rmsDeviation: Double = 0.0) {
constructor(json: JSONObject) : this() {
try {
if (json.has("dia")) dia = json.getDouble("dia")
if (json.has("meanDeviation")) meanDeviation = json.getDouble("meanDeviation")
if (json.has("SMRDeviation")) smrDeviation = json.getDouble("SMRDeviation")
if (json.has("RMSDeviation")) rmsDeviation = json.getDouble("RMSDeviation")
} catch (e: JSONException) {
}
}
fun toJSON(): JSONObject {
val crjson = JSONObject()
try {
crjson.put("dia", dia)
crjson.put("meanDeviation", meanDeviation.toInt())
crjson.put("SMRDeviation", smrDeviation)
crjson.put("RMSDeviation", rmsDeviation.toInt())
} catch (e: JSONException) {
}
return crjson
}
}

View file

@ -0,0 +1,29 @@
package info.nightscout.androidaps.plugins.general.autotune.data
import org.json.JSONException
import org.json.JSONObject
class PeakDeviation(var peak: Int = 0, var meanDeviation: Double = 0.0, var smrDeviation: Double = 0.0, var rmsDeviation: Double = 0.0) {
constructor(json: JSONObject) : this() {
try {
if (json.has("peak")) peak = json.getInt("peak")
if (json.has("meanDeviation")) meanDeviation = json.getDouble("meanDeviation")
if (json.has("SMRDeviation")) smrDeviation = json.getDouble("SMRDeviation")
if (json.has("RMSDeviation")) rmsDeviation = json.getDouble("RMSDeviation")
} catch (e: JSONException) {
}
}
fun toJSON(): JSONObject {
val crjson = JSONObject()
try {
crjson.put("peak", peak)
crjson.put("meanDeviation", meanDeviation.toInt())
crjson.put("SMRDeviation", smrDeviation)
crjson.put("RMSDeviation", rmsDeviation.toInt())
} catch (e: JSONException) {
}
return crjson
}
}

View file

@ -0,0 +1,117 @@
package info.nightscout.androidaps.plugins.general.autotune.data
import info.nightscout.androidaps.utils.DateUtil
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.util.*
class PreppedGlucose {
var crData: List<CRDatum> = ArrayList()
var csfGlucoseData: List<BGDatum> = ArrayList()
var isfGlucoseData: List<BGDatum> = ArrayList()
var basalGlucoseData: List<BGDatum> = ArrayList()
var diaDeviations: List<DiaDeviation> = ArrayList()
var peakDeviations: List<PeakDeviation> = ArrayList()
var from: Long = 0
lateinit var dateUtil: DateUtil
// to generate same king of json string than oref0-autotune-prep
override fun toString(): String {
return toString(0)
}
constructor(from: Long, crData: List<CRDatum>, csfGlucoseData: List<BGDatum>, isfGlucoseData: List<BGDatum>, basalGlucoseData: List<BGDatum>, dateUtil: DateUtil) {
this.from = from
this.crData = crData
this.csfGlucoseData = csfGlucoseData
this.isfGlucoseData = isfGlucoseData
this.basalGlucoseData = basalGlucoseData
this.dateUtil = dateUtil
}
constructor(json: JSONObject?, dateUtil: DateUtil) {
if (json == null) return
this.dateUtil = dateUtil
crData = ArrayList()
csfGlucoseData = ArrayList()
isfGlucoseData = ArrayList()
basalGlucoseData = ArrayList()
try {
crData = JsonCRDataToList(json.getJSONArray("CRData"))
csfGlucoseData = JsonGlucoseDataToList(json.getJSONArray("CSFGlucoseData"))
isfGlucoseData = JsonGlucoseDataToList(json.getJSONArray("ISFGlucoseData"))
basalGlucoseData = JsonGlucoseDataToList(json.getJSONArray("basalGlucoseData"))
} catch (e: JSONException) {
}
}
private fun JsonGlucoseDataToList(array: JSONArray): List<BGDatum> {
val bgData: MutableList<BGDatum> = ArrayList()
for (index in 0 until array.length()) {
try {
val o = array.getJSONObject(index)
bgData.add(BGDatum(o, dateUtil))
} catch (e: Exception) {
}
}
return bgData
}
private fun JsonCRDataToList(array: JSONArray): List<CRDatum> {
val crData: MutableList<CRDatum> = ArrayList()
for (index in 0 until array.length()) {
try {
val o = array.getJSONObject(index)
crData.add(CRDatum(o, dateUtil))
} catch (e: Exception) {
}
}
return crData
}
fun toString(indent: Int): String {
var jsonString = ""
val json = JSONObject()
try {
val crjson = JSONArray()
for (crd in crData) {
crjson.put(crd.toJSON())
}
val csfjson = JSONArray()
for (bgd in csfGlucoseData) {
csfjson.put(bgd.toJSON(true))
}
val isfjson = JSONArray()
for (bgd in isfGlucoseData) {
isfjson.put(bgd.toJSON(false))
}
val basaljson = JSONArray()
for (bgd in basalGlucoseData) {
basaljson.put(bgd.toJSON(false))
}
val diajson = JSONArray()
val peakjson = JSONArray()
if (diaDeviations.size > 0 || peakDeviations.size > 0) {
for (diad in diaDeviations) {
diajson.put(diad.toJSON())
}
for (peakd in peakDeviations) {
peakjson.put(peakd.toJSON())
}
}
json.put("CRData", crjson)
json.put("CSFGlucoseData", csfjson)
json.put("ISFGlucoseData", isfjson)
json.put("basalGlucoseData", basaljson)
if (diaDeviations.size > 0 || peakDeviations.size > 0) {
json.put("diaDeviations", diajson)
json.put("peakDeviations", peakjson)
}
jsonString = if (indent != 0) json.toString(indent) else json.toString()
} catch (e: JSONException) {
}
return jsonString
}
}

View file

@ -0,0 +1,5 @@
package info.nightscout.androidaps.plugins.general.autotune.events
import info.nightscout.androidaps.events.Event
class EventAutotuneUpdateGui : Event()

View file

@ -69,11 +69,24 @@ class MaintenancePlugin @Inject constructor(
val files = logDir.listFiles { _: File?, name: String ->
(name.startsWith("AndroidAPS") && name.endsWith(".zip"))
}
val autotunefiles = logDir.listFiles { _: File?, name: String ->
(name.startsWith("autotune") && name.endsWith(".zip"))
}
val amount = sp.getInt(R.string.key_logshipper_amount, keep)
val keepIndex = amount - 1
if (autotunefiles != null && autotunefiles.isNotEmpty()) {
Arrays.sort(autotunefiles) { f1: File, f2: File -> f2.name.compareTo(f1.name) }
var delAutotuneFiles = listOf(*autotunefiles)
if (keepIndex < delAutotuneFiles.size) {
delAutotuneFiles = delAutotuneFiles.subList(keepIndex, delAutotuneFiles.size)
for (file in delAutotuneFiles) {
file.delete()
}
}
}
if (files == null || files.isEmpty()) return
Arrays.sort(files) { f1: File, f2: File -> f2.name.compareTo(f1.name) }
var delFiles = listOf(*files)
val amount = sp.getInt(R.string.key_logshipper_amount, keep)
val keepIndex = amount - 1
if (keepIndex < delFiles.size) {
delFiles = delFiles.subList(keepIndex, delFiles.size)
for (file in delFiles) {

View file

@ -0,0 +1,343 @@
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".plugins.general.autotune.AutotuneFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.card.MaterialCardView
style="@style/Widget.MaterialComponents.CardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_marginTop="4dp"
app:cardCornerRadius="4dp"
app:contentPadding="2dp"
app:cardElevation="2dp"
app:cardUseCompatPadding="false"
android:layout_gravity="center">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|center_vertical"
android:layout_weight="2"
android:gravity="end"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:text="@string/autotune_profile"
android:textSize="14sp" />
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@string/autotune_select_profile"
android:paddingStart="5dp"
android:paddingEnd="5dp">
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/profileList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:paddingTop="5dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|center_vertical"
android:layout_weight="2"
android:gravity="end"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:text="@string/autotune_tune_days"
android:textSize="14sp" />
<info.nightscout.androidaps.utils.ui.NumberPicker
android:id="@+id/tune_days"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:layout_weight="1"
android:layout_marginBottom="2dp"
app:customContentDescription="@string/careportal_newnstreatment_duration_label" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
style="@style/Widget.MaterialComponents.CardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_marginTop="4dp"
app:cardCornerRadius="4dp"
app:contentPadding="2dp"
app:cardElevation="2dp"
app:cardUseCompatPadding="false"
android:layout_gravity="center">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:gravity="end"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:text="@string/autotune_last_run"
android:textSize="14sp" />
<TextView
android:id="@+id/tune_lastrun"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:gravity="end"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:text="@string/autotune_warning"
android:textSize="14sp" />
<TextView
android:id="@+id/tune_warning"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
style="@style/Widget.MaterialComponents.CardView"
android:id="@+id/autotune_results_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_marginTop="4dp"
app:cardCornerRadius="4dp"
app:contentPadding="2dp"
app:cardElevation="2dp"
app:cardUseCompatPadding="false"
android:layout_gravity="center">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/autotune_results"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
</LinearLayout>
<androidx.gridlayout.widget.GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dip"
app:columnCount="2">
<com.google.android.material.button.MaterialButton
android:id="@+id/autotune_profileswitch"
style="@style/GrayButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:text="@string/activate_profile"
app:icon="@drawable/ic_local_activate"
app:iconPadding="-6dp"
app:iconTint="@color/ic_local_activate"
app:layout_column="0"
app:layout_columnWeight="1"
app:layout_gravity="fill"
app:layout_row="0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/autotune_compare"
style="@style/GrayButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:text="@string/autotune_compare_profile"
app:icon="@drawable/ic_compare_profiles"
app:iconPadding="-6dp"
app:iconTintMode="multiply"
app:layout_column="1"
app:layout_columnWeight="1"
app:layout_gravity="fill"
app:layout_row="0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/autotune_copylocal"
style="@style/GrayButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:text="@string/autotune_copy_localprofile_button"
app:icon="@drawable/ic_clone_48"
app:iconPadding="-6dp"
app:iconTintMode="multiply"
app:layout_column="0"
app:layout_columnWeight="1"
app:layout_gravity="fill"
app:layout_row="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/autotune_update_profile"
style="@style/GrayButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:text="@string/autotune_update_input_profile_button"
app:icon="@drawable/ic_local_save"
app:iconPadding="-8dp"
app:iconTint="@color/ic_local_save"
app:layout_column="1"
app:layout_columnWeight="1"
app:layout_gravity="fill"
app:layout_row="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/autotune_revert_profile"
style="@style/GrayButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:text="@string/autotune_revert_input_profile_button"
android:visibility="gone"
app:icon="@drawable/ic_local_reset"
app:iconPadding="-6dp"
app:iconTint="@color/ic_local_reset"
app:layout_column="1"
app:layout_columnWeight="1"
app:layout_gravity="fill"
app:layout_row="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/autotune_run"
style="@style/GrayButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:text="@string/autotune_run"
app:icon="@drawable/ic_autotune"
app:iconPadding="-4dp"
app:iconTint="@color/ic_local_save"
app:layout_column="0"
app:layout_columnWeight="1"
app:layout_gravity="fill"
app:layout_row="2" />
<com.google.android.material.button.MaterialButton
android:id="@+id/autotune_check_input_profile"
style="@style/GrayButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:text="@string/autotune_check_input_profile_button"
app:icon="@drawable/ic_home_profile"
app:iconPadding="-6dp"
app:iconTint="?attr/profileColor"
app:layout_column="1"
app:layout_columnWeight="1"
app:layout_gravity="fill"
app:layout_row="2" />
</androidx.gridlayout.widget.GridLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View file

@ -442,6 +442,7 @@
<string name="enableuam_summary">Detekce neoznámených jídel</string>
<string name="insulin_oref_peak">Čas vrcholu IOB křivky</string>
<string name="insulin_peak_time">Vrchol křivky [min]</string>
<string name="insulin_peak">Vrchol</string>
<string name="free_peak_oref">Volitelný vrchol - Oref</string>
<string name="rapid_acting_oref">Rychle působící - Oref</string>
<string name="ultrarapid_oref">Ultra rychlý - Oref</string>

View file

@ -104,9 +104,11 @@
<string name="troubleshooting_wheretoask">Hvor kan du søge efter hjælp til AndroidAPS?</string>
<string name="troubleshooting_fb">Du kan bede om råd i \"AndroidAPS-users\" Facebook-gruppen.</string>
<string name="troubleshooting_wiki">Du bør læse (og genlæse) AndroidAPS dokumentation.</string>
<string name="troubleshooting_gitter">Du kan bede om råd og logge tekniske problemer eller andre problemer i AndroidAPS Discord.</string>
<string name="troubleshooting_yourendo">Du bør spørge din diabetessygeplejerske/endokrinolog.</string>
<string name="troubleshooting_hint1">https://androidaps.readthedocs.io/en/latest/EN/Installing-AndroidAPS/Update-to-new-version.html#troubleshooting</string>
<string name="troubleshooting_hint2">https://www.facebook.com/groups/AndroidAPSUsers/</string>
<string name="troubleshooting_hint3">https://discord.gg/4fQUWHZ4Mw</string>
<string name="insulin_label">Insulin Plugins</string>
<string name="insulin_ultrarapid">Hvilken insulin skal du bruge sammen med Ultra-Rapid Oref-pluginnet?</string>
<string name="insulin_fiasp">Fiasp®</string>

View file

@ -25,6 +25,7 @@
<string name="description_ns_client">Synkroniserer dine data med NightScout</string>
<string name="description_ama">Status for algoritmen i 2017</string>
<string name="description_smb">Seneste algoritme for avancerede brugere</string>
<string name="description_smb_dynamic_isf">Seneste algoritme for avancerede brugere med dynamisk/automatisk ISF</string>
<string name="description_overview">Viser den aktuelle tilstand af dit loop og knapper til de mest almindelige handlinger</string>
<string name="description_persistent_notification">Viser en løbende notifikation med en kort oversigt over, hvad dit loop gør</string>
<string name="description_profile_local">Definér en profil, der er tilgængelig offline.</string>
@ -160,6 +161,7 @@
<string name="end_user_license_agreement">Slutbrugerlicensaftale</string>
<string name="end_user_license_agreement_text">MÅ IKKE BRUGES TIL AT TRÆFFE MEDICINSKE BESLUTNINGER. DER ER INGEN GARANTI FOR PROGRAMMET, I DET OMFANG GÆLDENDE LOV TILLADER DET. UNDTAGEN NÅR DET ELLERS ER ANFØRT SKRIFTLIGT, AT RETTIGHEDSHAVERE OG / ELLER ANDRE PARTER LEVERER PROGRAMMET \"SOM BESET\" UDEN NOGEN FORM FOR GARANTI, HVERKEN UDTRYKT ELLER UNDERFORSTÅET, HERUNDER, MEN IKKE BEGRÆNSET TIL, DE UNDERFORSTÅELIGHEDER, DER ER FORBUNDET MED EGNETHED OG EGNETHED TIL ET BESTEMT FORMÅL. HELE RISIKOEN MED HENSYN TIL KVALITETEN OG YDEEVNEN AF PROGRAMMET ER DIN. HVIS PROGRAMMET VISER SIG AT VÆRE DEFEKT, BÆRER DU SELV OMKOSTNINGERNE VED ALLE NØDVENDIGE SERVICER, REPARATIONER ELLER KORREKTIONER SOM ER NØDVENDIGE.</string>
<string name="end_user_license_agreement_i_understand">JEG FORSTÅR OG ER ENIG</string>
<string name="save">Gem</string>
<string name="reloadprofile">Genindlæs profil</string>
<string name="smscommunicator">SMS Kommunikator</string>
<string name="smscommunicator_allowednumbers">Tilladte telefonnumre</string>
@ -244,6 +246,7 @@
<string name="wear">Ur</string>
<string name="resend_all_data">Send alle data igen</string>
<string name="open_settings_on_wear">Åbn indstillinger på ur</string>
<string name="basal_rate">Basalrate</string>
<string name="basalvaluebelowminimum">Basal værdi under minimum. Profil ikke angivet!</string>
<string name="sms_actualbg">BG:</string>
<string name="sms_lastbg">Sidste BG:</string>
@ -264,6 +267,7 @@
<string name="configbuilder_shortname">KONF</string>
<string name="loop_shortname">LOOP</string>
<string name="oaps_shortname">OAPS</string>
<string name="dynisf_shortname">DynISF</string>
<string name="localprofile_shortname">LP</string>
<string name="overview_shortname">HJEM</string>
<string name="virtualpump_shortname">VPUMPE</string>
@ -429,6 +433,9 @@
<string name="ns_localbroadcasts">Aktiver udsendelse til andre apps (såsom xDrip+). Aktiver ikke hvis du har mere end én version af AAPS eller NSClient installeret!</string>
<string name="ns_localbroadcasts_title">Aktiver lokale udsendelser.</string>
<string name="openapssmb">OpenAPS SMB</string>
<string name="openaps_smb_dynamic_isf">Dynamisk ISF</string>
<string name="DynISFAdjust_title">DynamicISF-justeringsfaktor %%</string>
<string name="DynISFAdjust_summary">Justeringsfaktor for Dynamisk ISF. Indstil mere end 100%% for mere aggressive korrektionsdoser og mindre end 100%% for mindre aggressive korrektioner.</string>
<string name="enableuam">Aktiver UAM</string>
<string name="enablesmb">Aktiver SMB</string>
<string name="enablesmb_summary">Brug Super Mikro Boluser i stedet for midlertidig basal for hurtigere handling</string>
@ -496,6 +503,7 @@
<string name="negativeonly">Kun negative</string>
<string name="overview_editquickwizard_usecob">COB beregning</string>
<string name="overview_editquickwizard_usetemptarget">Midlertidig mål beregning</string>
<string name="overview_editquickwizard_usepercentage">Beregning i %</string>
<string name="loopenabled">Loop aktiveret</string>
<string name="apsselected">APS valgt</string>
<string name="nsclienthaswritepermission">NSClient har skrivetilladelse</string>
@ -656,6 +664,10 @@
<string name="sensitivity_raises_target_title">Følsomhed hæver midlertidige mål</string>
<string name="sensitivity_raises_target_summary">Når der påvises følsomhed, skal målglukosen hæves</string>
<string name="careportal_removestartedevents">Ren AndroidAPS startet</string>
<string name="show_invalidated">Vis ugyldige</string>
<string name="hide_invalidated">Skjul ugyldige</string>
<string name="remove_items">Fjern elementer</string>
<string name="sort_items">Sortér elementer</string>
<string name="storedsettingsfound">Gemte indstillinger fundet</string>
<string name="allow_hardware_pump_text">Bemærk: Hvis du aktiverer og opretter forbindelse til en hardwarepumpe, vil AndroidAPS kopiere de basale indstillinger fra profilen til pumpen, overskrive den eksisterende basal rate lagret på pumpen. Sørg for, at du har den korrekte basal indstilling i AndroidAPS. Hvis du ikke er sikker på eller ikke ønsker at overskrive de basale indstillinger på din pumpe, tryk på annuller og gentag skift til pumpen på et senere tidspunkt.</string>
<string name="error_adding_treatment_title">Behandlingsdata ukomplette</string>
@ -751,6 +763,8 @@
<string name="profilenamecontainsdot">Profilnavn indeholder punktum.\nDette understøttes ikke af NS.\nProfilen er ikke uploadet til NS.</string>
<string name="low_mark_comment">Nedre værdi for målområde (kun visning)</string>
<string name="high_mark_comment">Øvre værdi for målområde (kun visning)</string>
<string name="age">Alder</string>
<string name="weight_label">Vægt</string>
<string name="id">ID:</string>
<string name="submit">Send</string>
<string name="mostcommonprofile">Mest almindelige profil:</string>
@ -800,6 +814,7 @@
<string name="smscommunicator_otp_install_info">På hver follower telefon installeres Authenticator app, der understøtter RFC 6238 TOTP tokens. Populære gratis apps er:\n • Authy\n • Google Authenticator\n • LastPass Authenticator\n • FreeOTP Authenticator</string>
<string name="smscommunicator_otp_reset_warning">Ved at nulstille autentificering gør du alle allerede proviserede autentificatorer ugyldige. Du bliver nødt til at opsætte dem igen!</string>
<string name="overview_show_predictions">Forudsigelser</string>
<string name="overview_show_treatments">Behandlinger</string>
<string name="overview_show_deviationslope">Afvigelses hældning</string>
<string name="authorizationfailed">Godkendelse mislykkedes</string>
<string name="overview_show_absinsulin">Absolut insulin</string>
@ -869,6 +884,8 @@
<string name="ns_receive_profile_switch_summary">Accepter profil skift indtastet gennem NS eller NSClient</string>
<string name="ns_receive_offline_event">Modtag APS offline begivenheder</string>
<string name="ns_receive_offline_event_summary">Accepter APS Offline begivenheder indtastet gennem NS eller NSClient</string>
<string name="ns_receive_tbr_eb">Modtag TBR og EB</string>
<string name="ns_receive_tbr_eb_summary">Accepter TBR og EB indtastet gennem en anden instans</string>
<string name="ns_receive_insulin">Modtag insulin</string>
<string name="ns_receive_insulin_summary">Accepter insulin via NS eller NSClient (det er ikke afgivet, kun beregnet til IOB)</string>
<string name="ns_receive_carbs">Modtag kulhydrater</string>
@ -891,6 +908,7 @@
<string name="errors">Fejl</string>
<string name="ns_sync_slow">Reducer upload hastighed</string>
<string name="data_status">BG data status</string>
<string name="remove_bg_readings">Fjern BG aflæsninger</string>
<string name="statuslights_cannula_age">Indstik alder</string>
<string name="statuslights_patch_pump_age">patch pumpe alder</string>
<string name="patch_pump">Patch pumpe</string>
@ -898,8 +916,110 @@
<string name="bg_too_close">BG for tæt:\n%1$s\n%2$s</string>
<string name="identification">Identifikation (e-mail, FB eller Discord alias osv.)</string>
<string name="identification_not_set">Identifikation ikke indstillet i udvikler-tilstand</string>
<string name="a11y_dialog">dialog</string>
<string name="a11y_current_bg">nuværende blodglukose</string>
<string name="a11_correction_percentage">korrekt resultat med %</string>
<string name="a11_correction_units">korrekt resultat med enheder</string>
<string name="not_available_full">Ikke tilgængelig</string>
<string name="a11y_high">høj</string>
<string name="a11y_inrange">inden for området</string>
<string name="a11y_low">lav</string>
<string name="a11y_arrow_double_down">falder hurtigt</string>
<string name="a11y_arrow_single_down">falder</string>
<string name="a11y_arrow_forty_five_down">falder langsomt</string>
<string name="a11y_arrow_flat">stabil</string>
<string name="a11y_arrow_forty_five_up">stiger langsomt</string>
<string name="a11y_arrow_single_up">stiger</string>
<string name="a11y_arrow_double_up">stiger hurtigt</string>
<string name="a11y_arrow_none">ingen</string>
<string name="a11y_arrow_unknown">ukendt</string>
<string name="a11y_graph">graf</string>
<string name="a11y_bg_quality">blodglukose kvalitet</string>
<string name="a11y_bg_quality_recalculated">genberegnet</string>
<string name="a11y_bg_quality_doubles">dobbelt postering</string>
<string name="a11y_insulin_label">insulin</string>
<string name="a11y_blood_glucose">blodglucose</string>
<string name="a11y_bg_outdated">forældet</string>
<string name="a11y_carb_reminder">Indstil påmindelse</string>
<string name="a11y_add_new_profile">tilføj ny profil</string>
<string name="a11y_clone_profile">klon nuværende profil</string>
<string name="a11y_delete_current_profile">slet nuværende profil</string>
<string name="a11y_add_new_to_list">tilføj ny til listen</string>
<!-- Theme switcher dark and light mode-->
<string name="theme_switcher_summary">Vælg mørk, lys eller følg systemtemaet</string>
<string name="app_color_scheme">App Farvetema</string>
<string name="dark_theme">Mørkt tema</string>
<string name="light_theme">Lyst tema</string>
<string name="follow_system_theme">Brug enhedens tema</string>
<!-- WEAR OS-->
<string name="wear_action_tempt_preset_error">Midlertidigmål ukendt forudindstilling: %1$s</string>
<string name="wear_action_tempt_cancel_message">Annullér aktuelt midlertidig mål?</string>
<string name="wear_action_tempt_unit_error">Forskellige enheder brugt på ur og telefon!</string>
<string name="wear_action_tempt_zero_message">0-mål - annuller midlertidigt mål?</string>
<string name="wear_action_tempt_min_bg_error">Min-BS udenfor området!</string>
<string name="wear_action_tempt_max_bg_error">Max-BS udenfor området!</string>
<string name="wear_action_tempt_manual_range_message">Midlertidigt mål:\nMin: %1$s\nMax: %2$s\nVarighed: %3$s</string>
<string name="wear_action_tempt_manual_message">Midlertigt mål:\nMål: %1$s\nVarighed: %2$s</string>
<string name="wear_action_tempt_preset_message">Midlertigt mål:\Grund: %1$s\nMål: %2$s\nVarighed: %3$s</string>
<string name="quick_wizard_message">Hurtigguide: %1$s\nInsulin: %2$.2fE\nKH: %3$dg</string>
<string name="wizard_result">Guide:\nInsulin: %1$.2fE\nKH: %2$dg</string>
<string name="overview_editquickwizard_show_on_device">Vis post på enhed:</string>
<string name="quick_wizard_not_available">Valgt guide er ikke længere tilgængeligt. Opdater venligst din widget</string>
<string name="wizard_no_actual_bg">Ingen nylig BG til at basere beregningen på!</string>
<string name="wizard_no_active_profile">Ingen aktiv profil angivet!</string>
<string name="wizard_no_cob">Ukendt COB! BG læsning mangler eller nylig app genstart?</string>
<string name="wizard_carbs_constraint">KH begrænsninger overtrådt!</string>
<string name="wizard_explain_calc">Calc (IC: %2$.1f, ISF: %2$.1f) fra:\"</string>
<string name="wizard_explain_carbs">Kulhydrater: %1$.2fE</string>
<string name="wizard_explain_cob">COB: %1$.0fg %2$.2fE</string>
<string name="wizard_explain_bg">BS: %1$.2fE</string>
<string name="wizard_explain_basal_iob">Basal IOB: %1$.2fE</string>
<string name="wizard_explain_bolus_iob">Bolus IOB: %1$.2fE</string>
<string name="wizard_explain_superbolus">Superbolus: %1$.2fE</string>
<string name="wizard_explain_trend">15\' trend: %1$.2fE</string>
<string name="wizard_explain_percent">Procent: %1$.2fE x %2$d%% ≈ %3$.2fE</string>
<string name="wizard_constraint_bolus_size">Overtrædelse af insulinbegrænsning!\nKan ikke levere %1$.2fE</string>
<string name="wizard_explain_tt">Midl: %1$s</string>
<string name="wizard_explain_tt_to">%1$s til %2$s</string>
<string name="wizard_pump_not_available">Ingen pumpe tilgængelig!</string>
<string name="wear_unknown_action_string">Ukendt kommando:</string>
<string name="overview_editquickwizard_percentage">Procentdel</string>
<string name="app_default">Program standard</string>
<string name="show_invalidated_records">Vis ugyldige / fjernede poster</string>
<string name="hide_invalidated_records">Skjul ugyldige / fjernede poster</string>
<string name="select_profile">Vælg profil, du vil redigere</string>
<string name="refresh_from_nightscout">Opdater fra Nightscout</string>
<string name="remove_selected_items">Fjern valgte elementer</string>
<string name="select_for_removal">Vælg for at fjerne</string>
<string name="profile_changes">Profil ændringer</string>
<string name="tempt_targets">Midlertidig mål </string>
<string name="carbs_and_bolus">Kulhydrater og bolus</string>
<string name="confirm_remove_multiple_items">Er du sikker på, at du vil fjerne %1$d elementer?</string>
<string name="no_records_available">Ingen poster tilgængelige</string>
<string name="hide_loop">Skjul loop</string>
<string name="show_loop">Vis loop</string>
<string name="count_selected">%1$d valgt</string>
<string name="sort_label">Sortér</string>
<string name="dialog_canceled">Dialog annulleret</string>
<string name="below" comment="below &quot;in range&quot;">Under</string>
<string name="in_range">Inden for området</string>
<string name="above" comment="above &quot;in range&quot;">Over</string>
<string name="show_loop_records">Vis loop poster</string>
<string name="show_hide_records">Skjul loop poster</string>
<string name="widget_description">AndroidAPS widget</string>
<string name="configure">Indstil gennemsigtighed</string>
<string name="loop_status">Loop status</string>
<string name="a11y_otp_qr_code">QR-kode til opsætning af engangs kodeord</string>
<string name="a11y_open_settings">Åbn indstillinger</string>
<string name="a11y_set_carb_timer">indstil KH alarm</string>
<string name="device_all">Alle</string>
<string name="device_phone">Telefon</string>
<string name="device_watch">Ur</string>
<string name="a11y_only_on_watch">kun på ur</string>
<string name="a11y_only_on_phone">kun på telefon</string>
<string name="a11y_drag_and_drop_handle">træk og slip håndtering</string>
<!-- Aidex Cgms -->
<string name="aidex">GlucoRx Aidex</string>
<string name="aidex_short">Aidex</string>
<string name="description_source_aidex">Modtag BG-værdier fra GlucoRx Aidex CGMS.</string>
</resources>

View file

@ -39,7 +39,7 @@
<string name="codeaccepted">Código aceito</string>
<string name="codeinvalid">Código inválido</string>
<string name="objectives_exam_objective">Prove seu conhecimento</string>
<string name="objectives_exam_gate">Estude as perguntas. Haverá quatro respostas possíveis para cada pergunta. Pode ter mais do que uma resposta correta. Por favor, marque todas as que estão corretas e selecione VERIFY.</string>
<string name="objectives_exam_gate">Estude as perguntas. Haverá quatro respostas possíveis para cada pergunta. Pode ter mais do que uma resposta correta. Por favor, marque todas as que estão corretas e selecione VERIFICAR.</string>
<string name="answerdisabledto">Resposta desativada até: %1$s</string>
<string name="wronganswer">Resposta errada!</string>
<string name="unfinshed_button">Próximo inacabado</string>
@ -49,7 +49,7 @@
<string name="usetemptarget_hint" formatted="false">https://androidaps.readthedocs.io/en/latest/EN/Getting-Started/Screenshots.html#the-homescreen</string>
<string name="useaction_hint" formatted="false">https://androidaps.readthedocs.io/en/latest/EN/Getting-Started/Screenshots.html#config-builder</string>
<string name="usescale_hint" formatted="false">https://androidaps.readthedocs.io/en/latest/EN/Getting-Started/Screenshots.html#the-homescreen</string>
<string name="notconnected">Não está ligado à internet</string>
<string name="notconnected">Não está conectado à internet</string>
<string name="failedretrievetime">Falha no tempo de recuperação</string>
<string name="requirementnotmet">Requisitos de objetivo não cumpridos</string>
</resources>

View file

@ -285,9 +285,9 @@
<string name="openapsama_autosens_adjusttargets">Autosens também ajusta os alvos</string>
<string name="openapsama_autosens_adjusttargets_summary">Valor padrão: true\nÉ usado para permitir que autosens possa ajustar os valores alvo de glucose no sangue (BG), além de ISF e basais.</string>
<string name="openapsama_bolussnooze_dia_divisor_summary">Valor padrão: 2\nBolus Snooze (pausa após bolus) é executado depois de realizar um bolus por refeição Desta maneira o algoritmo não irá contrariar com temporárias baixas logo depois da refeição. O valor padrão é 2; Então uma duração de ação da insulina (DIA) de 5h significa que o Bolus Snooze irá ser gradualmente reduzido ao longo de 2,5 horas = 5/2 = DIA/Valor padrão.</string>
<string name="openapsama_min_5m_carbimpact_summary">Valor padrão: 3.0 para detecção avançada de refeições (AMA) ou 8.0 para super micro bolus (SMB). Esta é a configuração padrão para o calculo de quanto varia a cada 5 min a glucose no sangue (BG) devido à absorção de carboidratos. O padrão é 3mg/ dl / 5min. Isso afeta a rapidez com que decaem as calorias no corpo (COB), e quantos carboidratos terão de ser considerados no cálculo da previsão de BG, quando é que a BG está baixando mais do que o esperado ou não subindo como esperado.</string>
<string name="openapsama_link_to_preferncejson_doc_txt">Atenção!\n Normalmente não é necessário modificar os valores abaixo. Por favor PRESSIONE AQUI e LEIA o texto para garantir que ENTENDE as consequenciais antes de alterar algum destes valores.</string>
<string name="smscommunicator_invalidphonennumber">SMS número de telefone inválido</string>
<string name="openapsama_min_5m_carbimpact_summary">Valor padrão: 3.0 para assitência avançada de refeições (AAR) ou 8.0 para super micro bolus (SMB). Esta é a configuração padrão para o calculo de quanto varia a cada 5 min a glicemia (BG) devido à absorção de carboidratos. O padrão é 3mg/dl/5min. Isso afeta a rapidez com que decaem os carboidratos ativos (CA) e quanta absorção de carboidrato será considerada no cálculo da previsão de glicemia futura, tornando possível notar que glicemia está baixando mais do que o esperado ou não subindo como esperado.</string>
<string name="openapsama_link_to_preferncejson_doc_txt">Atenção!\n Normalmente não é necessário modificar os valores abaixo. Por favor PRESSIONE AQUI e LEIA o texto para garantir que ENTENDE as consequências antes de alterar qualquer um destes valores.</string>
<string name="smscommunicator_invalidphonennumber">Número de telefone inválido para comunicação por SMS</string>
<string name="overview_calibration">Calibração</string>
<string name="xdripnotinstalled">xDrip+ não está instalado</string>
<string name="calibrationsent">Calibração enviada para o xDrip+</string>
@ -309,41 +309,41 @@
<string name="nsclientinternal_secret_dialogtitle">NS API secret</string>
<string name="nsclientinternal_secret_dialogmessage">Insira NS API secret (min 12 caract.)</string>
<string name="deliver_now">Entregar agora</string>
<string name="clear_queue">Limpar fila de espera</string>
<string name="clear_queue">Limpar fila</string>
<string name="show_queue">Mostrar fila</string>
<string name="queue">Fila:</string>
<string name="status">Status:</string>
<string name="clearlog">Limpar Log</string>
<string name="clearlog">Limpar registros</string>
<string name="nowritepermission">NSCLIENT não tem permissão de escrita. Senha da API errada?</string>
<string name="wear_settings">Definições Wear</string>
<string name="wear_detailedIOB_title">Mostrar IOB detalhado</string>
<string name="wear_detailedIOB_summary">Dividir IOB entre IOB de bolus e de basal na face do relógio</string>
<string name="wear_detailedIOB_title">Mostrar detalhes da IA</string>
<string name="wear_detailedIOB_summary">Dividir IA entre IA de bolus e de basal na face do relógio</string>
<string name="nosuccess">não foi bem sucedido - por favor, verifique o telefone</string>
<string name="notavailable">n/a</string>
<string name="patientage">Tipo de paciente</string>
<string name="child">Criança</string>
<string name="teenage">Adolescente</string>
<string name="adult">Adulto</string>
<string name="resistantadult">Adulto resistente insulina</string>
<string name="resistantadult">Adulto resistente à insulina</string>
<string name="pregnant">Grávida</string>
<string name="patientage_summary">Selecione o tipo de paciente para configurar os limites de segurança</string>
<string name="patient_name">Nome do Paciente</string>
<string name="patient_name_summary">Por favor, forneça nome do paciente ou apelido para diferenciar entre várias configurações</string>
<string name="patient_name_default" comment="This is default patient display name, when user does not provide real one">Usuário</string>
<string name="Glimp">Glimp</string>
<string name="needwhitelisting">%1$s necessita de autorizar a não optimização da bateria para assegurar a performance necessária</string>
<string name="needwhitelisting">%1$s necessita autorização de execução sem otimização da bateria para assegurar a performance necessária</string>
<string name="loopsuspended">Loop suspenso</string>
<string name="loopsuspendedfor">Suspendido (%1$d m)</string>
<string name="loopsuspendedfor">Suspenso (%1$d m)</string>
<string name="suspendloopfor1h">Suspender loop por 1h</string>
<string name="suspendloopfor2h">Suspender loop por 2h</string>
<string name="suspendloopfor3h">Suspender loop por 3h</string>
<string name="suspendloopfor10h">Suspender loop por 10h</string>
<string name="disconnectpump">Bomba Desconectada</string>
<string name="disconnectpumpfor15m">Desligar bomba por 15 min</string>
<string name="disconnectpumpfor30m">Desligar bomba por 30 min</string>
<string name="disconnectpumpfor1h">Desligar bomba por 1 h</string>
<string name="disconnectpumpfor2h">Desligar bomba por 2 h</string>
<string name="disconnectpumpfor3h">Desligar bomba por 3 h</string>
<string name="disconnectpumpfor15m">Desconectar bomba por 15 min</string>
<string name="disconnectpumpfor30m">Desconectar bomba por 30 min</string>
<string name="disconnectpumpfor1h">Desconectar bomba por 1 h</string>
<string name="disconnectpumpfor2h">Desconectar bomba por 2 h</string>
<string name="disconnectpumpfor3h">Desconectar bomba por 3 h</string>
<string name="duration15m">15 min</string>
<string name="duration30m">30 min</string>
<string name="duration1h">1 hora</string>
@ -356,15 +356,15 @@
<string name="smscommunicator_loopsuspended">Loop suspenso</string>
<string name="smscommunicator_loopresumed">Loop retomado</string>
<string name="bg_trend_label">Tendência 15 min</string>
<string name="treatments_wizard_cob_label">COB</string>
<string name="superbolus">Superbólus</string>
<string name="ns_logappstartedevent">Registar inicio da app no NS</string>
<string name="restartingapp">A sair da aplicação para aplicar as configurações.</string>
<string name="configbuilder_insulin_description">Qual o tipo de insulina que está a utilizar?</string>
<string name="treatments_wizard_cob_label">CA</string>
<string name="superbolus">Superbolus</string>
<string name="ns_logappstartedevent">Registrar início do app no NS</string>
<string name="restartingapp">Saindo do app para aplicar as configurações.</string>
<string name="configbuilder_insulin_description">Que tipo de insulina está usando?</string>
<string name="fastactinginsulincomment">Novorapid, Novolog, Humalog</string>
<string name="ultrafastactinginsulincomment">Fiasp</string>
<string name="insulin_shortname">INS</string>
<string name="enablesuperbolus">Ativar superbólus no assistente</string>
<string name="enablesuperbolus">Ativar superbolus no assistente</string>
<string name="enablesuperbolus_summary">Habilite a funcionalidade de superbolus no assistente. Não habilite até que aprenda o funcionamento. PODE CAUSAR OVERDOSE DE INSULINA SE USAR INDISCRIMINADAMENTE!</string>
<string name="show_statuslights">Mostrar luzes de estado no ecrã principal</string>
<string name="statuslights_cage_warning">Aviso de limite da vida útil da cânula [h]</string>
@ -377,14 +377,14 @@
<string name="statuslights_sbat_critical">Aviso de limite crítico do nível da bateria do sensor [%]</string>
<string name="statuslights_bage_warning">Aviso de limite da vida útil da bateria da bomba [h]</string>
<string name="statuslights_bage_critical">Aviso de limite crítico da vida útil da bateria da bomba [h]</string>
<string name="statuslights_res_warning">Limite de aviso de nível de reservatório [U]</string>
<string name="statuslights_res_critical">Limite crítico de nível de reservatório [U]</string>
<string name="statuslights_res_warning">Aviso de limite de nível de reservatório [U]</string>
<string name="statuslights_res_critical">Aviso de limite crítico de nível de reservatório [U]</string>
<string name="activity_shortname">ACT</string>
<string name="nav_about">Sobre</string>
<string name="smscommunicator_missingsmspermission">Falta de permissão SMS</string>
<string name="smscommunicator_missingphonestatepermission">Falta permissão do estado do telefone</string>
<string name="xdripstatus_shortname">xds</string>
<string name="wear_showbgi_title">Mostrar BGI</string>
<string name="wear_showbgi_title">Mostrar IG (Impacto na Glicemia)</string>
<string name="wear_showbgi_summary">Adicionar BGI à linha de status</string>
<string name="overview_extendedbolus_cancel_button">Cancelar Bólus Estendido</string>
<string name="doprofileswitch">Fazer Mudança De Perfil</string>

View file

@ -442,6 +442,7 @@
<string name="enableuam_summary">Detekcia neoznámených jedál</string>
<string name="insulin_oref_peak">Čas vrcholu IOB krivky</string>
<string name="insulin_peak_time">Vrchol krivky [min]</string>
<string name="insulin_peak">Vrchol</string>
<string name="free_peak_oref">Voliteľný vrchol - Oref</string>
<string name="rapid_acting_oref">Rýchlo pôsobiaci - Oref</string>
<string name="ultrarapid_oref">Ultra rýchly - Oref</string>

View file

@ -533,6 +533,7 @@
<string name="key_insulin_oref_peak" translatable="false">insulin_oref_peak</string>
<string name="insulin_oref_peak">IOB Curve Peak Time</string>
<string name="insulin_peak_time">Peak Time [min]</string>
<string name="insulin_peak">Peak</string>
<string name="free_peak_oref">Free-Peak Oref</string>
<string name="rapid_acting_oref">Rapid-Acting Oref</string>
<string name="ultrarapid_oref">Ultra-Rapid Oref</string>

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
android:key="@string/key_autotune_plugin"
android:title="@string/autotune_settings"
app:initialExpandedChildrenCount="10">
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_autotune_auto"
android:summary="@string/autotune_auto_summary"
android:title="@string/autotune_auto_title" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_autotune_categorize_uam_as_basal"
android:summary="@string/autotune_categorize_uam_as_basal_summary"
android:title="@string/autotune_categorize_uam_as_basal_title" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_autotune_tune_insulin_curve"
android:summary="@string/autotune_tune_insulin_curve_summary"
android:title="@string/autotune_tune_insulin_curve_title" />
<EditTextPreference
android:defaultValue="5"
android:inputType="number"
android:key="@string/key_autotune_default_tune_days"
android:summary="@string/autotune_default_tune_days_summary"
android:title="@string/autotune_default_tune_days_title" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_autotune_circadian_ic_isf"
android:summary="@string/autotune_circadian_ic_isf_summary"
android:title="@string/autotune_circadian_ic_isf_title" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_autotune_additional_log"
android:summary="@string/autotune_additional_log_summary"
android:title="@string/autotune_additional_log_title" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>

View file

@ -42,6 +42,7 @@ abstract class AutomationModule {
@ContributesAndroidInjector abstract fun actionCarePortalEventInjector(): ActionCarePortalEvent
@ContributesAndroidInjector abstract fun actionProfileSwitchInjector(): ActionProfileSwitch
@ContributesAndroidInjector abstract fun actionProfileSwitchPercentInjector(): ActionProfileSwitchPercent
@ContributesAndroidInjector abstract fun actionRunAutotuneInjector(): ActionRunAutotune
@ContributesAndroidInjector abstract fun actionSendSMSInjector(): ActionSendSMS
@ContributesAndroidInjector abstract fun actionStartTempTargetInjector(): ActionStartTempTarget
@ContributesAndroidInjector abstract fun actionStopTempTargetInjector(): ActionStopTempTarget

View file

@ -332,6 +332,7 @@ class AutomationPlugin @Inject constructor(
ActionCarePortalEvent(injector),
ActionProfileSwitchPercent(injector),
ActionProfileSwitch(injector),
ActionRunAutotune(injector),
ActionSendSMS(injector)
)
}

View file

@ -71,6 +71,8 @@ abstract class Action(val injector: HasAndroidInjector) {
ActionProfileSwitch::class.java.simpleName -> ActionProfileSwitch(injector).fromJSON(data.toString())
ActionProfileSwitchPercent::class.java.name,
ActionProfileSwitchPercent::class.java.simpleName -> ActionProfileSwitchPercent(injector).fromJSON(data.toString())
ActionRunAutotune::class.java.name,
ActionRunAutotune::class.java.simpleName -> ActionRunAutotune(injector).fromJSON(data.toString())
ActionSendSMS::class.java.name,
ActionSendSMS::class.java.simpleName -> ActionSendSMS(injector).fromJSON(data.toString())
ActionStartTempTarget::class.java.name,

View file

@ -0,0 +1,89 @@
package info.nightscout.androidaps.plugins.general.automation.actions
import android.widget.LinearLayout
import androidx.annotation.DrawableRes
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.automation.R
import info.nightscout.androidaps.data.PumpEnactResult
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Autotune
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.general.automation.elements.InputDuration
import info.nightscout.androidaps.plugins.general.automation.elements.InputProfileName
import info.nightscout.androidaps.plugins.general.automation.elements.LabelWithElement
import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.utils.JsonHelper
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONObject
import javax.inject.Inject
class ActionRunAutotune(injector: HasAndroidInjector) : Action(injector) {
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var autotunePlugin: Autotune
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var sp: SP
@Inject lateinit var uel: UserEntryLogger
var defaultValue = 0
private var inputProfileName = InputProfileName(rh, activePlugin, "", true)
private var daysBack = InputDuration(0, InputDuration.TimeUnit.DAYS)
override fun friendlyName(): Int = R.string.autotune_run
override fun shortDescription(): String = resourceHelper.gs(R.string.autotune_profile_name, inputProfileName.value)
@DrawableRes override fun icon(): Int = R.drawable.ic_actions_profileswitch
override fun doAction(callback: Callback) {
val autoSwitch = sp.getBoolean(R.string.key_autotune_auto, false)
val profileName = if (inputProfileName.value == rh.gs(R.string.active)) "" else inputProfileName.value
var message = if (autoSwitch) R.string.autotune_run_with_autoswitch else R.string.autotune_run_without_autoswitch
Thread {
autotunePlugin.atLog("[Automation] Run Autotune $profileName, ${daysBack.value} days, Autoswitch $autoSwitch")
autotunePlugin.aapsAutotune(daysBack.value, autoSwitch, profileName)
if (!autotunePlugin.lastRunSuccess) {
message = R.string.autotune_run_with_error
aapsLogger.error(LTag.AUTOMATION, "Error during Autotune Run")
}
callback.result(PumpEnactResult(injector).success(autotunePlugin.lastRunSuccess).comment(message))?.run()
}.start()
return
}
override fun generateDialog(root: LinearLayout) {
if (defaultValue == 0)
defaultValue = sp.getInt(R.string.key_autotune_default_tune_days, 5)
daysBack.value = defaultValue
LayoutBuilder()
.add(LabelWithElement(rh, rh.gs(R.string.autotune_select_profile), "", inputProfileName))
.add(LabelWithElement(rh, rh.gs(R.string.autotune_tune_days), "", daysBack))
.build(root)
}
override fun hasDialog(): Boolean = true
override fun toJSON(): String {
val data = JSONObject()
.put("profileToTune", inputProfileName.value)
.put("tunedays", daysBack.value)
return JSONObject()
.put("type", this.javaClass.name)
.put("data", data)
.toString()
}
override fun fromJSON(data: String): Action {
val o = JSONObject(data)
inputProfileName.value = JsonHelper.safeGetString(o, "profileToTune", "")
defaultValue = JsonHelper.safeGetInt(o, "tunedays")
if (defaultValue == 0)
defaultValue = sp.getInt(R.string.key_autotune_default_tune_days, 5)
daysBack.value = defaultValue
return this
}
override fun isValid(): Boolean = profileFunction.getProfile() != null && activePlugin.getSpecificPluginsListByInterface(Autotune::class.java).first().isEnabled()
}

View file

@ -13,7 +13,7 @@ class InputDuration(
) : Element() {
enum class TimeUnit {
MINUTES, HOURS
MINUTES, HOURS, DAYS
}
override fun addToLayout(root: LinearLayout) {
@ -21,6 +21,9 @@ class InputDuration(
if (unit == TimeUnit.MINUTES) {
numberPicker = MinutesNumberPicker(root.context, null)
numberPicker.setParams(value.toDouble(), 5.0, 24 * 60.0, 10.0, DecimalFormat("0"), false, root.findViewById(R.id.ok))
} else if (unit == TimeUnit.DAYS) {
numberPicker = MinutesNumberPicker(root.context, null)
numberPicker.setParams(value.toDouble(), 1.0, 30.0, 1.0, DecimalFormat("0"), false, root.findViewById(R.id.ok))
} else {
numberPicker = NumberPicker(root.context, null)
numberPicker.setParams(value.toDouble(), 1.0, 24.0, 1.0, DecimalFormat("0"), false, root.findViewById(R.id.ok))

View file

@ -25,7 +25,7 @@ class InputPercent() : Element() {
companion object {
const val MIN = 70.0
const val MIN = 50.0
const val MAX = 130.0
}
}

View file

@ -10,14 +10,15 @@ import info.nightscout.androidaps.automation.R
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.ResourceHelper
class InputProfileName(private val rh: ResourceHelper, private val activePlugin: ActivePlugin, val name: String = "") : Element() {
class InputProfileName(private val rh: ResourceHelper, private val activePlugin: ActivePlugin, val name: String = "", val addActive: Boolean = false) : Element() {
var value: String = name
override fun addToLayout(root: LinearLayout) {
val profileStore = activePlugin.activeProfileSource.profile ?: return
val profileList = profileStore.getProfileList()
if (addActive)
profileList.add(0, rh.gs(R.string.active))
root.addView(
Spinner(root.context).apply {
adapter = ArrayAdapter(root.context, R.layout.spinner_centered, profileList).apply {

View file

@ -120,4 +120,7 @@
<string name="confirm_remove_multiple_items">Opravdu chcete odstranit %1$d položek</string>
<string name="sort_label">Seřadit</string>
<string name="system_automation">Automatizace systému</string>
<string name="run_automations">Spustit automatizace</string>
<string name="add_automation">Přidat pravidlo</string>
<string name="remove_sort">Odstranit/řadit</string>
</resources>

View file

@ -81,6 +81,7 @@
<string name="wifissidcompared">WiFi SSID %1$s %2$s</string>
<string name="autosenscompared">Autosens %1$s %2$s %%</string>
<string name="autosenslabel">Autosens %</string>
<string name="a11y_autosenslabel">Auto sens</string>
<string name="deltacompared">%3$s %1$s %2$s</string>
<string name="deltalabel">BS forskel</string>
<string name="deltalabel_u">BS difference [%1$s]</string>
@ -112,4 +113,14 @@
<string name="automation_event">Automatiserings event</string>
<string name="reorder_label">Omorganisering</string>
<string name="user_action">Brugerhandling</string>
<string name="remove_automation">Fjern automatisering</string>
<string name="sort_automation">Sortér automatisering</string>
<string name="remove_selected_items">Fjern valgte emner</string>
<string name="count_selected">%1$d valgt</string>
<string name="confirm_remove_multiple_items">Er du sikker på, at du vil fjerne %1$d elementer?</string>
<string name="sort_label">Sortér</string>
<string name="system_automation">System automatisering</string>
<string name="run_automations">Kør automatisering</string>
<string name="add_automation">Tilføj regel</string>
<string name="remove_sort">Fjern/sorter</string>
</resources>

View file

@ -120,4 +120,6 @@
<string name="confirm_remove_multiple_items">Êtes-vous sûr de vouloir supprimer %1$d entrée(s)</string>
<string name="sort_label">Trier</string>
<string name="system_automation">Système d\'automatisation</string>
<string name="add_automation">Ajouter une règle</string>
<string name="remove_sort">Supprimer/trier</string>
</resources>

View file

@ -9,7 +9,7 @@ buildscript {
rxkotlin_version = '3.0.1'
room_version = '2.4.2'
lifecycle_version = '2.4.1'
dagger_version = '2.41'
dagger_version = '2.42'
coroutines_version = '1.6.1'
activity_version = '1.3.1'
fragmentktx_version = '1.3.6'
@ -43,7 +43,7 @@ buildscript {
maven { url "https://plugins.gradle.org/m2/" } // jacoco 0.2
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.3'
classpath 'com.android.tools.build:gradle:7.2.0'
classpath 'com.google.gms:google-services:4.3.10'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'

View file

@ -51,4 +51,10 @@
<string name="combo_tbr_count">TBR antal</string>
<string name="bolusstopped">Bolus stoppet</string>
<string name="bolusstopping">Stopper bolus</string>
<string name="pump_commerror_label">Komm. Fejlantal</string>
<string name="show_comm_error_count_title">Vis komm. fejltælling</string>
<string name="show_comm_error_count_summary">Viser antal fejl, når du kommunikerer med Ruffy. I de fleste tilfælde betyder tal højere end 0 at Ruffy oplever kommunikationsproblemer (genstart kan være nødvendigt).</string>
<string name="combo_error_display_never">Aldrig</string>
<string name="combo_error_display_error">Ved Fejl</string>
<string name="combo_error_display_always">Altid</string>
</resources>

View file

@ -0,0 +1,45 @@
package info.nightscout.androidaps.data
import info.nightscout.androidaps.database.entities.Bolus
import kotlin.math.exp
import kotlin.math.pow
class LocalInsulin constructor(val name:String?, val peak:Int = DEFAULT_PEAK, private val userDefinedDia: Double = DEFAULT_DIA) {
val dia
get(): Double {
val dia = userDefinedDia
return if (dia >= MIN_DIA) {
dia
} else {
MIN_DIA
}
}
val duration
get() = (60 * 60 * 1000L * dia).toLong()
fun iobCalcForTreatment(bolus: Bolus, time: Long): Iob {
val result = Iob()
if (bolus.amount != 0.0) {
val bolusTime = bolus.timestamp
val t = (time - bolusTime) / 1000.0 / 60.0
val td = dia * 60 //getDIA() always >= MIN_DIA
val tp = peak.toDouble()
// force the IOB to 0 if over DIA hours have passed
if (t < td) {
val tau = tp * (1 - tp / td) / (1 - 2 * tp / td)
val a = 2 * tau / td
val S = 1 / (1 - a + (1 + a) * exp(-td / tau))
result.activityContrib = bolus.amount * (S / tau.pow(2.0)) * t * (1 - t / td) * exp(-t / tau)
result.iobContrib = bolus.amount * (1 - S * (1 - a) * ((t.pow(2.0) / (tau * td * (1 - a)) - t / tau - 1) * Math.exp(-t / tau) + 1))
}
}
return result
}
companion object {
private const val MIN_DIA = 5.0
private const val DEFAULT_DIA = 6.0
private const val DEFAULT_PEAK = 75
}
}

View file

@ -1,6 +1,7 @@
package info.nightscout.androidaps.extensions
import info.nightscout.androidaps.data.Iob
import info.nightscout.androidaps.data.LocalInsulin
import info.nightscout.androidaps.database.embedments.InterfaceIDs
import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.database.entities.TherapyEvent
@ -16,6 +17,12 @@ fun Bolus.iobCalc(activePlugin: ActivePlugin, time: Long, dia: Double): Iob {
return insulinInterface.iobCalcForTreatment(this, time, dia)
}
// Add specific calculation for Autotune (reference localInsulin for Peak/dia)
fun Bolus.iobCalc(time: Long, localInsulin: LocalInsulin): Iob {
if (!isValid || type == Bolus.Type.PRIMING ) return Iob()
return localInsulin.iobCalcForTreatment(this, time)
}
fun Bolus.toJson(isAdd: Boolean, dateUtil: DateUtil): JSONObject =
JSONObject()
.put("eventType", if (type == Bolus.Type.SMB) TherapyEvent.Type.CORRECTION_BOLUS.text else TherapyEvent.Type.MEAL_BOLUS.text)

View file

@ -0,0 +1,9 @@
package info.nightscout.androidaps.interfaces
interface Autotune {
fun aapsAutotune(daysBack: Int, autoSwitch: Boolean, profileToTune: String = ""): String
fun atLog(message: String)
var lastRunSuccess: Boolean
}

View file

@ -148,6 +148,10 @@ interface Profile {
return (passed / 1000).toInt()
}
fun milliSecFromMidnight(date: Long): Long {
val passed = DateTime(date).millisOfDay.toLong()
return passed
}
/*
* Units conversion
*/

View file

@ -285,6 +285,7 @@ class Translator @Inject internal constructor(
Sources.Aaps -> TODO()
*/
Sources.Automation -> rh.gs(R.string.automation)
Sources.Autotune -> rh.gs(R.string.autotune)
Sources.Loop -> rh.gs(R.string.loop)
Sources.NSClient -> rh.gs(R.string.ns)
Sources.Pump -> rh.gs(R.string.pump)

View file

@ -108,6 +108,7 @@ class UserEntryMapper {
Announcement (UserEntry.Sources.Announcement),
Actions (UserEntry.Sources.Actions),
Automation (UserEntry.Sources.Automation),
Autotune (UserEntry.Sources.Autotune),
BG (UserEntry.Sources.BG),
Aidex (UserEntry.Sources.Aidex),
Dexcom (UserEntry.Sources.Dexcom),

View file

@ -62,6 +62,7 @@ class UserEntryPresentationHelper @Inject constructor(
Sources.Announcement -> R.drawable.ic_cp_announcement
Sources.Actions -> R.drawable.ic_action
Sources.Automation -> R.drawable.ic_automation
Sources.Autotune -> R.drawable.ic_autotune
Sources.BG -> R.drawable.ic_generic_cgm
Sources.Aidex -> R.drawable.ic_blooddrop_48
Sources.Dexcom -> R.drawable.ic_dexcom_g6

View file

@ -455,7 +455,6 @@
<string name="autotune_circadian_ic_isf_summary">Autotune nebude ladit cirkadiánní rozložení, tato možnost použije pouze průměrné hodnoty IC a ISF na váš cirkadiánní vstupní profil</string>
<string name="autotune_additional_log_title">Zahrnout další informace o protokolu pro ladění</string>
<string name="autotune_additional_log_summary">Zapněte, pouze pokud je to požadováno vývojářem, aby bylo možné odeslat další logy pro ladění pluginu Autotune</string>
<string name="autotune_default_tune_days_summary">Výchozí počet dní, ze kterých mají být data zpracována Autotune (až xx)</string>
<string name="autotune_tunedprofile_name">Vyladěno</string>
<string name="autotune_profile">Profil :</string>
<string name="autotune_tune_days">Ladit dní:</string>
@ -464,7 +463,6 @@
<string name="autotune_select_profile">Vyberte profil pro ladění</string>
<string name="autotune_ic_warning">Autotune funguje pouze s jedinou hodnotou IC, váš profil má %1$d hodnot. Průměrná hodnota je %2$.2fg/U</string>
<string name="autotune_isf_warning">Autotune funguje pouze s jedinou hodnotou ISF, váš profil má %1$d hodnot. Průměrná hodnota je %2$.1f%3$s/U</string>
<string name="autotune_error">Chyba vstupních dat, zkuste snížit počet dní</string>
<string name="autotune_warning_during_run">Autotune spuštěno, prosím buďte trpěliví</string>
<string name="autotune_warning_after_run">Před použitím výsledky pečlivě zkontrolujte!</string>
<string name="autotune_partial_result">Částečný výsledek - vyladěn den %1$d / %2$d</string>

View file

@ -4,6 +4,7 @@
<!-- General-->
<string name="refresh">Opdatér</string>
<string name="error">Fejl</string>
<string name="save">Gem</string>
<string name="not_set_short">Ikke angivet</string>
<string name="failedupdatebasalprofile">Opdatering af basal profil mislykkedes</string>
<string name="profile_set_ok">Basal profil i pumpen er opdateret</string>
@ -41,6 +42,7 @@
<string name="carbs">Kulhydrater</string>
<string name="invalidprofile">Ugyldig profil !!!</string>
<string name="noprofileset">INGEN PROFIL SAT</string>
<string name="active"><![CDATA[<Active>]]></string>
<string name="date">Dato</string>
<string name="units_label">Enheder</string>
<string name="dia_label">DIA</string>
@ -110,6 +112,7 @@
<string name="notes_label">Bemærkninger</string>
<string name="remove_button">Fjern</string>
<string name="addnew">Tilføj ny</string>
<string name="addnew_above">Tilføj ny ovenfor</string>
<string name="wrong_pump_data">Data kommer fra en anden pumpe. Skift pumpedriver for at nulstille pumpetilstand.</string>
<!-- Constraints-->
<string name="limitingbasalratio">Begrænser max IOB til %1$.2f E/t på grund af %2$s</string>
@ -132,7 +135,9 @@
<string name="location_not_found_message">Placeringen skal være aktiveret, hvis Bluetooth-opdagelsen skal fungere på nyere enheder. AAPS sporer ikke din placering, og det kan deaktiveres efter parring er lykkedes.</string>
<!-- Protection-->
<string name="wrongpassword">Forkert kodeord</string>
<string name="wrongpin">Forkert pinkode</string>
<string name="passwords_dont_match">Kodeordene stemmer ikke overens</string>
<string name="pin_dont_match">PIN-koder ikke identiske</string>
<!-- Profile-->
<string name="basalprofilenotaligned">Basalværdier ikke angivet i hele timer: %1$s</string>
<string name="minimalbasalvaluereplaced">Basal dosis erstattet af minimal understøttet dosis: %1$s</string>
@ -352,6 +357,7 @@
<string name="uel_stat_reset">STAT NULSTIL</string>
<string name="uel_delete_logs">SLET LOGS</string>
<string name="uel_delete_future_treatments">SLET FREMTIDIGE BEHANDLINGER</string>
<string name="delete_future_treatments">Slet fremtidige behandlinger</string>
<string name="uel_export_settings">EKSPORTER INDSTILLINGER</string>
<string name="uel_import_settings">IMPORTER INDSTILLINGER</string>
<string name="uel_reset_databases">NULSTIL DATABASER</string>
@ -423,7 +429,61 @@
<string name="bolus_ok" comment="26 characters max for translation">Bolus OK</string>
<string name="pump_paired" comment="26 characters max for translation">Pumpe parret</string>
<string name="insight_refresh_button" comment="26 characters max for translation">Insight Opdater-knap</string>
<string name="a11y_min_button_description">fald %1$s af %2$s</string>
<string name="a11y_plus_button_description">stigning %1$s af %2$s</string>
<string name="formatPercent">%1$.0f%%</string>
<string name="basal">Basal</string>
<string name="basalpct">Basal %</string>
<string name="count_selected">%1$d valgt</string>
<string name="sort_label">Sortér</string>
<string name="remove_items">Fjern Emner</string>
<string name="sort_items">Sortér Emner</string>
<string name="remove_selected_items">Fjern valgte emner</string>
<string name="a11y_file">fil</string>
<string name="a11y_user">bruger</string>
<!-- Autotune -->
<string name="autotune">Autotune</string>
<string name="autotune_description">Hjælp til potentielle justeringer af profil (ISF, KH-ratio og basalrater)</string>
<string name="autotune_shortname">AT</string>
<string name="autotune_settings">Autotune indstillinger</string>
<string name="autotune_auto_title">Automatisk profilskift</string>
<string name="autotune_auto_summary">Hvis aktiveret, vil Autotune automatisk opdatere og skifte til input-profil efter beregning ud fra en automatiseringsregel.</string>
<string name="autotune_categorize_uam_as_basal_title">Kategoriser UAM som basal</string>
<string name="autotune_categorize_uam_as_basal_summary">Aktiver kun hvis du har indtastet alle kulhydrater på pålidelig vis. Med denne indstilling vil pludselige stigninger set af Autotune, blive brugt til at anbefale ændringer af basal rate.</string>
<string name="autotune_default_tune_days_title">Antal dage med data</string>
<string name="autotune_circadian_ic_isf_title">Anvend gennemsnitligt resultat i døgnbaseret IC/ISF</string>
<string name="autotune_circadian_ic_isf_summary">Autotune vil ikke justere døgnrytme variationer, denne indstilling benytter gennemsnitlige værdier til justering af IC og ISF til din døgnrytme input profil</string>
<string name="autotune_additional_log_title">Inkludér flere logoplysninger for fejlfinding</string>
<string name="autotune_additional_log_summary">Aktivér kun hvis udviklerne beder dig om det, for at sende flere logoplysninger til at hjælpe med at fejlfinde Autotune plugin</string>
<string name="autotune_tunedprofile_name">Justeret</string>
<string name="autotune_profile">Profil :</string>
<string name="autotune_tune_days">Justerings dage :</string>
<string name="autotune_last_run">Sidst kørt :</string>
<string name="autotune_warning">Advarsel :</string>
<string name="autotune_select_profile">Vælg profil, der skal justeres</string>
<string name="autotune_ic_warning">Autotune fungerer med kun én IC-værdi, din profil har %1$d værdier. Gennemsnitsværdi er %2$.2fg/E</string>
<string name="autotune_isf_warning">Autotune virker med kun én ISF-værdi, din profil har %1$d værdier. Gennemsnitlig værdi er %2$.1f%3$s/E</string>
<string name="autotune_warning_during_run">Automatisk beregning startet, vær venligst tålmodig</string>
<string name="autotune_warning_after_run">Kontrollér resultaterne omhyggeligt, før du bruger dem!</string>
<string name="autotune_partial_result">Delvis resultat dag %1$d / %2$d justeret</string>
<string name="autotune_result">Resultat: %1$s</string>
<string name="autotune_param">Parameter</string>
<string name="autotune_percent">%</string>
<string name="autotune_missing">Mangler</string>
<string name="autotune_profile_name">Autotune profil %1$s</string>
<string name="autotune_run">Kør Autotune</string>
<string name="autotune_check_input_profile_button">Tjek input-profil</string>
<string name="autotune_compare_profile">Sammenlign profiler</string>
<string name="autotune_copy_localprofile_button">Kopier til lokal profil</string>
<string name="autotune_update_input_profile_button">Opdater input profil</string>
<string name="autotune_revert_input_profile_button">Gendan input profil</string>
<string name="autotune_copy_local_profile_message">Opret en ny lokal profil ud fra denne Autotune profil?</string>
<string name="autotune_update_local_profile_message">Opdater %1$s profil med Autotune profil?</string>
<string name="autotune_revert_local_profile_message">Gendan %1$s profil med Input-profilen?</string>
<string name="autotune_profile_invalid">Profil ugyldig</string>
<string name="autotune_run_without_autoswitch">Autotune kørt uden profilskift</string>
<string name="autotune_run_with_autoswitch">Autotune kørt, profilen er automatisk skiftet</string>
<string name="autotune_run_with_error">Fejl under sidste Autotune kørsel</string>
<plurals name="days">
<item quantity="one">%1$d dag</item>
<item quantity="other">%1$d dage</item>

View file

@ -455,7 +455,6 @@
<string name="autotune_circadian_ic_isf_summary">Autotune no afinará las variaciones circadianas, esta opción solo aplica los ajustes medios de IC e ISF a tu perfil circadiano de entrada</string>
<string name="autotune_additional_log_title">Incluir más información de registro para depuración</string>
<string name="autotune_additional_log_summary">Activar sólo si es solicitado por los desarrolladores, para enviar más registros y así ayudar a depurar el plugin Autotune</string>
<string name="autotune_default_tune_days_summary">Número predeterminado de días de datos a procesar por Autotune (hasta xx)</string>
<string name="autotune_tunedprofile_name">Ajustado</string>
<string name="autotune_profile">Perfil :</string>
<string name="autotune_tune_days">Días de ajuste:</string>
@ -464,7 +463,6 @@
<string name="autotune_select_profile">Selecciona el perfil para a ajustar</string>
<string name="autotune_ic_warning">Autotune sólo funciona con un valor de IC. Tu perfil tiene %1$d valores. El valor promedio es %2$.2fg/U</string>
<string name="autotune_isf_warning">Autotune sólo funciona con un valor de ISF. Tu perfil tiene %1$d valores. El valor promedio es %2$.1f%3$s/U</string>
<string name="autotune_error">Error en los datos de entrada, intenta reducir el número de días</string>
<string name="autotune_warning_during_run">Cálculo de autototune iniciado, por favor ten paciencia</string>
<string name="autotune_warning_after_run">¡Comprueba los resultados cuidadosamente antes de usarlos!</string>
<string name="autotune_partial_result">Resultado parcial día %1$d / %2$d afinado</string>

View file

@ -455,7 +455,6 @@
<string name="autotune_circadian_ic_isf_summary">Autotune ne réglera pas les variations circadiennes, cette option ne fait que appliquer le réglage moyen du G/I et de la SI à votre profil d\'entrée circadien</string>
<string name="autotune_additional_log_title">Inclure plus d\'informations de log pour le débogage</string>
<string name="autotune_additional_log_summary">A n\'activer que sur demande du développeur pour envoyer plus d\'informations dans les fichiers log pour aider à déboguer le plugin Autotune</string>
<string name="autotune_default_tune_days_summary">Nombre de jours de données par défaut à traiter par Autotune (jusqu\'à 30)</string>
<string name="autotune_tunedprofile_name">Tuned</string>
<string name="autotune_profile">Profil :</string>
<string name="autotune_tune_days">Nb jours :</string>
@ -464,7 +463,6 @@
<string name="autotune_select_profile">Sélectionnez le profil à optimiser</string>
<string name="autotune_ic_warning">Autotune ne fonctionne qu\'avec une seule valeur G/I, votre profil a %1$d valeurs. La valeur moyenne est de %2$.2f g/U</string>
<string name="autotune_isf_warning">Autotune ne calcule qu\'une seule valeur de SI, votre profil a %1$d valeurs. La valeur moyenne est de %2$.1f %3$s/U</string>
<string name="autotune_error">Erreur dans les données d\'entrée, essayez de relancer le calcul ou réduire le nombre de jours</string>
<string name="autotune_warning_during_run">Le calcul Autotune a commencé, veuillez patienter</string>
<string name="autotune_warning_after_run">Vérifiez attentivement les résultats avant de les utiliser!</string>
<string name="autotune_partial_result">Résultat partiel jour %1$d / %2$d calculé</string>

View file

@ -42,6 +42,7 @@
<string name="carbs">Karbohydrater</string>
<string name="invalidprofile">Ugyldig profil!!!</string>
<string name="noprofileset">INGEN PROFIL VALGT</string>
<string name="active"><![CDATA[<Active>]]></string>
<string name="date">Dato</string>
<string name="units_label">Enheter</string>
<string name="dia_label">DIA</string>
@ -441,6 +442,48 @@
<string name="a11y_file">fil</string>
<string name="a11y_user">bruker</string>
<!-- Autotune -->
<string name="autotune">Autotune</string>
<string name="autotune_description">Hjelp til å justere profilen (insulinfølsomhet, karbohydratfaktor og basal doser)</string>
<string name="autotune_shortname">AT</string>
<string name="autotune_settings">Autotune innstillinger</string>
<string name="autotune_auto_title">Automatisering profilbytte</string>
<string name="autotune_auto_summary">Hvis det er aktivert så vil Autotune automatisk oppdatere profilen og skifte til denne etter beregninger fra en automatiserings regel.</string>
<string name="autotune_categorize_uam_as_basal_title">Kategoriser UAM (uannonsert måltid) som basal</string>
<string name="autotune_categorize_uam_as_basal_summary">Aktiver kun hvis du korrekt har angitt alle spiste karbohydrater. Med dette alternativet vil Autotune foreslå endringer i basal doser hvis den plutselig oppdager BS stigninger.</string>
<string name="autotune_default_tune_days_title">Antall dager med data</string>
<string name="autotune_circadian_ic_isf_title">Bruk gjennomsnittsverdier i cirkadisk IK/ISF</string>
<string name="autotune_circadian_ic_isf_summary">Autotune vil ikke justere cirkadiske variasjoner. Dette valget benytter gjennomsnittsverdier til å justere IK og ISF i din cirkadiske inngangsprofil</string>
<string name="autotune_additional_log_title">Inkluder mer logginformasjon for feilsøking</string>
<string name="autotune_additional_log_summary">Slå på kun dersom det er forespurt av utviklere for å sende mer logginformasjon for å hjelpe i feilsøking av Autotune</string>
<string name="autotune_tunedprofile_name">Innstilt</string>
<string name="autotune_profile">Profil :</string>
<string name="autotune_tune_days">Antall dager:</string>
<string name="autotune_last_run">Siste beregning :</string>
<string name="autotune_warning">Varsel:</string>
<string name="autotune_select_profile">Velg profil du vil justere</string>
<string name="autotune_ic_warning">Autotune fungerer bare med én IC-verdi, og din profil har %1$d verdier. Gjennomsnittlig verdi er %2$.2fg/E</string>
<string name="autotune_isf_warning">Autotune fungerer med bare én ISF verdi, og din profil har %1$d verdier. Gjennomsnittsverdi er %2$.1f%3$s/E</string>
<string name="autotune_warning_during_run">Har startet Autotune beregning, vennligst vent</string>
<string name="autotune_warning_after_run">Kontroller resultatene nøye før du bruker dem!</string>
<string name="autotune_partial_result">Delvis resultat dag %1$d / %2$d justert</string>
<string name="autotune_result">Resultat: %1$s</string>
<string name="autotune_param">Parametre</string>
<string name="autotune_percent">%</string>
<string name="autotune_missing">Mangler</string>
<string name="autotune_profile_name">Autotune profil %1$s</string>
<string name="autotune_run">Kjør Autotune</string>
<string name="autotune_check_input_profile_button">Sjekk inngangsprofil</string>
<string name="autotune_compare_profile">Sammenlign profiler</string>
<string name="autotune_copy_localprofile_button">Kopier til lokal profil</string>
<string name="autotune_update_input_profile_button">Oppdater inngangsprofil</string>
<string name="autotune_revert_input_profile_button">Tilbakestill inngangsprofil</string>
<string name="autotune_copy_local_profile_message">Opprette ny lokal profil fra denne Autotune profilen?</string>
<string name="autotune_update_local_profile_message">Oppdater %1$s profil med Autotune profil?</string>
<string name="autotune_revert_local_profile_message">Tilbakestill %1$s profil med inngangsprofil?</string>
<string name="autotune_profile_invalid">Ugyldig profil</string>
<string name="autotune_run_without_autoswitch">Autotune utført uten profilbytte</string>
<string name="autotune_run_with_autoswitch">Autotune utført og profil automatisk skiftet ut</string>
<string name="autotune_run_with_error">Feil oppdaget under siste Autotune kjøring</string>
<plurals name="days">
<item quantity="one">%1$d dag</item>
<item quantity="other">%1$d dager</item>

View file

@ -450,12 +450,14 @@
<string name="autotune_auto_summary">Pokiaľ je táto voľba povolená, Autotune po výpočte automaticky aktualizuje a prepne na vstupný profil zadaný v pravidle automatizácie.</string>
<string name="autotune_categorize_uam_as_basal_title">Započítavať UAM ako bazál</string>
<string name="autotune_categorize_uam_as_basal_summary">Povoľte iba v prípade, že ste dôsledne zadávali všetky prijaté sacharidy. S touto možnosťou bude náhly vzostup detekovaný funkciou Autotune, použitý pre doporučovanie zmien bazálu.</string>
<string name="autotune_tune_insulin_curve_title">Ladiť inzulínovú krivku</string>
<string name="autotune_tune_insulin_curve_summary">Aktivuj iba ak používaš voliteľný vrchol. Táto možnosť má vyladiť vrchol a trvanie DIA</string>
<string name="autotune_default_tune_days_title">Počet dní s dátami</string>
<string name="autotune_circadian_ic_isf_title">Použi priemerný výsledok v cirkadiánnom IC/ISF</string>
<string name="autotune_circadian_ic_isf_summary">Autotune nebude ladiť cirkadiánne rozloženie, táto možnosť použije iba priemerné ladenie IC a ISF na váš cirkadiánný vstupní profil</string>
<string name="autotune_additional_log_title">Zahrnúť ďalšie informácie o protokole pre ladenie</string>
<string name="autotune_additional_log_summary">Zapnite, iba pokiaľ je to požadované vývojárom, aby bolo možné odoslať dalšie logy pre ladenie modulu Autotune</string>
<string name="autotune_default_tune_days_summary">Štandardný počet dní, z ktorých majú byť dáta spracované Autotune (až do xx)</string>
<string name="autotune_default_tune_days_summary">Štandardný počet dní, z ktorých majú byť dáta spracované Autotune (až do 30)</string>
<string name="autotune_tunedprofile_name">Vyladené</string>
<string name="autotune_profile">Profil :</string>
<string name="autotune_tune_days">Ladiť dni:</string>
@ -464,7 +466,7 @@
<string name="autotune_select_profile">Vyberte profil pre ladenie</string>
<string name="autotune_ic_warning">Autotune funguje iba s jedinou hodnotou IC, váš profil má %1$d hodnôt. Priemerná hodnota je %2$.2fg/JI</string>
<string name="autotune_isf_warning">Autotune funguje iba s jedinou hodnotou IC, váš profil má %1$d hodnôt. Priemerná hodnota je %2$.1fg/JI</string>
<string name="autotune_error">Chyba vstupných dát, skúste znížiť počet dní</string>
<string name="autotune_error">Chyba vstupných dát, skúste znova spustiť Autotune, alebo znížte počet dní</string>
<string name="autotune_warning_during_run">Autotune spustený, prosím buďte trpezliví</string>
<string name="autotune_warning_after_run">Pred použitím výsledky starostlivo skontrolujte!</string>
<string name="autotune_partial_result">Čiastočný výsledok - vyladený deň %1$d / %2$d</string>

View file

@ -455,7 +455,6 @@
<string name="autotune_circadian_ic_isf_summary">OtoAyar sirkadiyen varyasyonları ayarlamaz, bu seçenek sirkadiyen giriş profilinize yalnızca IC ve İDF\'nün ortalama ayarını uygular</string>
<string name="autotune_additional_log_title">Hata ayıklama için daha fazla günlük bilgisi ekleyin</string>
<string name="autotune_additional_log_summary">OtoAyar eklentisinde hata ayıklamaya yardımcı olmak için yalnızca geliştirici tarafından daha fazla günlük bilgisi göndermesi istenirse açın</string>
<string name="autotune_default_tune_days_summary">OtoAyar tarafından işlenecek varsayılan veri gün sayısı (en fazla xx)</string>
<string name="autotune_tunedprofile_name">Ayarlandı</string>
<string name="autotune_profile">Profil :</string>
<string name="autotune_tune_days">Ayar günleri:</string>
@ -464,7 +463,6 @@
<string name="autotune_select_profile">Ayarlanacak profili seçin</string>
<string name="autotune_ic_warning">OtoAyar yalnızca bir IC değeriyle çalışır, profilinizde %1$d değer mevcut. Ortalama değer: %2$.2fg/Ü</string>
<string name="autotune_isf_warning">OtoAyar yalnızca bir İDF değeriyle çalışır, profilinizde %1$d değer mevcut. Ortalama değer: %2$.1f%3$s/Ü</string>
<string name="autotune_error">Giriş verilerinde hata, gün sayısını azaltmaya çalışın</string>
<string name="autotune_warning_during_run">OtoAyar hesaplaması başladı, lütfen sabırlı olun</string>
<string name="autotune_warning_after_run">Kullanmadan önce sonuçları dikkatlice kontrol edin!</string>
<string name="autotune_partial_result">Kısmi sonuç günü %1$d / %2$d ayarlandı</string>

View file

@ -68,6 +68,7 @@
<string name="key_insulin_oref_peak" translatable="false">insulin_oref_peak</string>
<string name="key_autotune_auto" translatable="false">autotune_auto</string>
<string name="key_autotune_categorize_uam_as_basal" translatable="false">categorize_uam_as_basal</string>
<string name="key_autotune_tune_insulin_curve" translatable="false">autotune_tune_insulin_curve</string>
<string name="key_autotune_default_tune_days" translatable="false">autotune_default_tune_days</string>
<string name="key_autotune_circadian_ic_isf" translatable="false">autotune_circadian_ic_isf</string>
<string name="key_autotune_additional_log" translatable="false">autotune_additional_log</string>
@ -554,12 +555,14 @@
<string name="autotune_auto_summary">If enabled, Autotune will automatically update and switch to input profile after calculation from an automation rule.</string>
<string name="autotune_categorize_uam_as_basal_title">Categorize UAM as basal</string>
<string name="autotune_categorize_uam_as_basal_summary">Enable only if you have reliably entered all carbs eaten, with this option sudden rises seen by Autotune will be used to recommend changes to the basal rate.</string>
<string name="autotune_tune_insulin_curve_title">Tune insulin curve</string>
<string name="autotune_tune_insulin_curve_summary">Enable only if you use free peak. This option will tune peak and DIA durations</string>
<string name="autotune_default_tune_days_title">Number of days of data</string>
<string name="autotune_circadian_ic_isf_title">Apply average result in circadian IC/ISF</string>
<string name="autotune_circadian_ic_isf_summary">Autotune will not tune circadian variations, this option only apply the average tuning of IC and ISF to your circadian input profile</string>
<string name="autotune_additional_log_title">Include more log information for debugging</string>
<string name="autotune_additional_log_summary">Switch on only if requested by dev to send more log information to help debugging Autotune plugin</string>
<string name="autotune_default_tune_days_summary">Default number of days of data to be processed by Autotune (up to xx)</string>
<string name="autotune_default_tune_days_summary">Default number of days of data to be processed by Autotune (up to 30)</string>
<string name="autotune_tunedprofile_name">Tuned</string>
<string name="autotune_profile">Profile :</string>
<string name="autotune_tune_days">Tune days :</string>
@ -568,7 +571,7 @@
<string name="autotune_select_profile">Select profile to tune</string>
<string name="autotune_ic_warning">Autotune works with only one IC value, your profile has %1$d values. Average value is %2$.2fg/U</string>
<string name="autotune_isf_warning">Autotune works with only one ISF value, your profile has %1$d values. Average value is %2$.1f%3$s/U</string>
<string name="autotune_error">Error in input data, try to reduce the number of days</string>
<string name="autotune_error">Error in input data, try to run again autotune or reduce the number of days</string>
<string name="autotune_warning_during_run">Autotune calculation started, please be patient</string>
<string name="autotune_warning_after_run">Check the results carefully before using it!</string>
<string name="autotune_partial_result">Partial result day %1$d / %2$d tuned</string>
@ -589,8 +592,9 @@
<string name="autotune_profile_invalid">Profile invalid</string>
<string name="autotune_log_title" translatable="false">|Param|Profile|Tuned|%/Miss.\n</string>
<string name="autotune_log_separator" translatable="false">+------------------------------------------\n</string>
<string name="autotune_log_isf" translatable="false">| %1$4.4s |\t%2$3.3f |\t%3$3.3f |\n</string>
<string name="autotune_log_ic" translatable="false">| %1$4.4s |\t%2$3.3f |\t%3$3.3f |\n</string>
<string name="autotune_log_peak" translatable="false">| %1$4.4s |\t%2$d |\t%3$d |\n</string>
<string name="autotune_log_dia" translatable="false">| %1$4.4s |\t%2$3.1f |\t%3$3.1f |\n</string>
<string name="autotune_log_ic_isf" translatable="false">| %1$4.4s | %2$3.3f |\t%3$3.3f |\n</string>
<string name="autotune_log_basal" translatable="false">|\t%1$02.0f\t| %2$3.3f |%3$3.3f\t| %5$.0f%% / %4$d\n</string>
<string name="autotune_log_sum_basal" translatable="false">|\t∑\t|\t%1$3.1f |\t%2$3.1f |\n</string>
<string name="autotune_run_without_autoswitch">Autotune runned without profile switch</string>

View file

@ -1,5 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="danars_pairing">Parring</string>
<string name="danars_nodeviceavailable">Ingen enhed fundet indtil videre</string>
<string name="danars_pairingok">Parring OK</string>
<string name="danars_pairingtimedout">Parring fik timeout</string>
<string name="danars_waitingforpairing">Venter på parring på pumpen</string>
<string name="danarspump">Dana-i/RS</string>
<string name="danarspump_shortname">Dana</string>
<string name="description_pump_dana_rs">Pumpeintegration til DANA Diabecare RS og Dana-i pumper</string>
<string name="maxbolusviolation">Max bolus overtrædelse</string>
<string name="commanderror">Kommando fejl</string>
<string name="speederror">Hastighedsfejl</string>
<string name="insulinlimitviolation">Overtrædelse af insulingrænse</string>
<string name="boluserrorcode">Ønsket: %1$.2fU Afgivet: %2$.2fU Fejlkode: %3$s</string>
<string name="danar_valuenotsetproperly">Værdi ikke angivet korrekt</string>
<string name="danar_setbasalstep001">Indstil basal trin til 0,01 E/t</string>
<string name="resetpairing">Nulstil parringsinformation?</string>
<string name="dana_model">%1$s\nModel: %2$02X\nProtokol: %3$02X\nKode: %4$02X</string>
<string name="processinghistory">Behandler begivenhed</string>
<string name="danar_enableextendedbolus">Aktiver forlængede bolusser på pumpe</string>
<string name="overview_bolusprogress_delivered">Afgivet</string>
<string name="overview_bolusprogress_stoped">Stoppet</string>
<string name="unsupportedfirmware">Ikke understøttet pumpe firmware</string>
<string name="pumperror">Pumpe Fejl</string>
<string name="lowbattery">Lavt batteri</string>
<string name="basalcompare">Leverer mindre end forudindstillet basal rate</string>
<string name="pumpshutdown">Pumpe Nedlukning</string>
<string name="batterydischarged">Pumpe batteri afladet</string>
<string name="occlusion">Tilstopning</string>
<string name="emptyreservoir">Tomt reservoir</string>
<string name="checkshaft">Kontrollér aksel</string>
<string name="basalmax">Basal max</string>
<string name="dailymax">Daglig max</string>
<string name="bloodsugarmeasurementalert">Advarsel om måling af blodsukker</string>
<string name="remaininsulinalert">Resterende insulin niveau</string>
<string name="missedbolus">Glemt bolus</string>
<string name="invalidpairing">Ugyldig parringsinformation. Anmoder om ny parring</string>
<string name="gettingpumpstatus">Henter pumpestatus</string>
<string name="gettingextendedbolusstatus">Henter forlænget bolusstatus</string>
<string name="gettingbolusstatus">Henter bolus status</string>
<string name="gettingtempbasalstatus">Få midlertidig basal status</string>
<string name="gettingpumpsettings">Henter pumpeindstillinger</string>
<string name="gettingpumptime">Henter pumpetid</string>
<string name="largetimedifftitle">Stor Tidsforskel</string>
<string name="largetimediff">Stor tidsforskel:\nTid i pumpen varierer med mere end 1,5 time.\nJustér venligst tiden manuelt på pumpen og sørg for, at læsning af historikken fra pumpen ikke forårsager uventet adfærd.\nHvis det er muligt; fjern historik fra pumpen før du ændrer tiden eller deaktiverer closed loop i én DIA efter den sidste forkerte historik post, men minimum én DIA fra nu.</string>
<string name="pairfirst">Par venligst din pumpe med din telefon!</string>
<string name="approachingdailylimit">Nærmer sig daglig insulingrænse</string>
<string name="startingbolus">Starter bolus afgivelse</string>
<string name="waitingforestimatedbolusend">Venter på bolusafslutningen. Mangler %1$d sek.</string>
<string name="stoppingtempbasal">Stopper midlertidig basal</string>
<string name="settingextendedbolus">Indstiller forlænget bolus</string>
<string name="stoppingextendedbolus">Stopper forlænget bolus</string>
<string name="updatingbasalrates">Opdaterer basal rater</string>
<string name="settingtempbasal">Indstiller midlertidig basal</string>
<string name="waitingfortimesynchronization">Venter på tidssynkronisering (%1$d sek)</string>
<string name="wrongpumppassword">Forkert pumpe kodeord!</string>
<string name="danar_history_alarm">Alarmer</string>
<string name="danar_history_basalhours">Basal Timer</string>
<string name="danar_history_bolus">Bolusser</string>
<string name="danar_history_carbohydrates">Kulhydrater</string>
<string name="danar_history_dailyinsulin">Daglig insulin</string>
<string name="danar_history_errors">Fejl</string>
<string name="danar_history_glucose">Glukose</string>
<string name="danar_history_refill">Genopfyld</string>
<string name="danar_history_syspend">Suspendér</string>
<string name="danar_history_prime">Klargør</string>
<string name="danar_useroptions">Brugerindstillinger</string>
<string name="danar_timedisplay">Visning af tidsformat</string>
<string name="danar_buttonscroll">Knap rulning</string>
<string name="danar_beep">Bip ved tryk på knap</string>
<string name="danar_pumpalarm">Alarm</string>
<string name="danar_pumpalarm_sound">Lyd</string>
<string name="danar_pumpalarm_vibrate">Vibrér</string>
<string name="danar_pumpalarm_both">Begge</string>
<string name="danar_screentimeout">LCD tændt tid [seconds]</string>
<string name="danar_backlight">Bagbelysning tændt tid [seconds]</string>
<string name="danar_glucoseunits">Glukose enheder</string>
<string name="danar_shutdown">Nedlukning [hours]</string>
<string name="danar_lowreservoir">Lavt reservoir [Units]</string>
<string name="danar_saveuseroptions">Gem indstillinger på pumpen</string>
<string name="description_pump_dana_r">Pumpeintegration til DANA Diabecare R pumper</string>
<string name="description_pump_dana_r_korean">Pumpeintegration til DANA Diabecare R pumper, koreansk version</string>
<string name="description_pump_dana_r_v2">Pumpeintegration til DANA Diabecare R pumper med opgraderet firmware (v2)</string>
<string name="danarpump_shortname">DANA</string>
<string name="nobtadapter">Ingen bluetooth-adapter fundet</string>
<string name="devicenotfound">Valgte enhed ikke fundet</string>
<string name="danar_switchtouhmode">Skift tilstand fra E/d til E/t på pumpen</string>
<string name="danarkoreanpump">DanaR Koreansk</string>
<string name="danarpump">DanaR</string>
<string name="pumpdrivercorrected">Pumpe driver korrigeret</string>
<string name="danarv2pump">DanaRv2</string>
<string name="danar_disableeasymode">Deaktivér EasyUI-tilstand i pumpen</string>
<string name="profile_set_failed">Indstilling af basal profil mislykkedes</string>
<string name="danar_bluetooth_status">Bluetooth Status</string>
<string name="danar_iob_label">Pumpe IOB</string>
<string name="virtualpump_firmware_label">Firmware</string>
<string name="danar_pump_settings">Dana pumpe indstillinger</string>
<string name="timeformat12h">12t</string>
<string name="timeformat24h">24t</string>
<string name="option_on">Tændt</string>
<string name="option_off">Slukket</string>
<string name="danar_bt_name_title">DanaR Bluetooth enhed</string>
<string name="danars_password_title">Pumpe kodeord (kun v1)</string>
<string name="danar_password_title">Pumpe kodeord</string>
<string name="danar_useextended_title">Brug forlængede bolusser til &gt;200%%</string>
<string name="bolusspeed">Bolus hastighed</string>
<string name="selectedpump">Valgt pumpe</string>
<string name="rs_loginsulinchange_title">Log reservoir ændring</string>
<string name="rs_loginsulinchange_summary">Tilføj \"Insulin Skift\" begivenhed til careportal, når de registreres i historik</string>
<string name="rs_logcanulachange_title">Log indstik skift</string>
<string name="rs_logcanulachange_summary">Tilføj \"Indstik skift\" begivenhed til careportal, når de registreres i historik</string>
<string name="pin1">PIN1</string>
<string name="pin2">PIN2</string>
<string name="press_ok_on_the_pump">Tryk OK på pumpen\nog angiv 2 viste numre\nHold skærmen tændt på pumpen ved at trykke på minus knappen, indtil du er færdig med at indtaste kode.</string>
<string name="num1pin">1: (12 cifre)</string>
<string name="num2pin">2: (8 cifre)</string>
<string name="basal_bolus_step">Basal/bolus trin</string>
<string name="carbs_store_error">Kulhydrater blev sandsynligvis ikke gemt korrekt. Tjek manuelt og gem igen, hvis det er nødvendigt.</string>
</resources>

View file

@ -138,6 +138,7 @@ data class UserEntry(
Announcement,
Actions, //From Actions plugin
Automation, //From Automation plugin
Autotune, //From Autotune plugin
BG, //From BG plugin => Add One Source per BG Source for Calibration or Sensor Change
Aidex,
Dexcom,

View file

@ -1,6 +1,157 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="resetpairing">Nulstil parring</string>
<string name="diaconn_nodeviceavailable">Ingen enhed tilgængelig</string>
<string name="diaconn_pairing">Diaconn Pumpe Parring</string>
<string name="pumperror">Pumpe Fejl</string>
<string name="diaconn_g8_history_alarm">Alarmer</string>
<string name="diaconn_g8_history_basalhours">Basal Timer</string>
<string name="diaconn_g8_history_bolus">Bolusser</string>
<string name="diaconn_g8_history_dailyinsulin">Daglig insulin</string>
<string name="diaconn_g8_history_errors">Fejl</string>
<string name="diaconn_g8_history_prime">Klargør</string>
<string name="diaconn_g8_history_refill">Genopfyld</string>
<string name="diaconn_g8_history_suspend">Suspendér</string>
<string name="diaconn_g8_pairingok">Parring OK</string>
<string name="diaconn_g8_waitingforpairing">Venter på parring</string>
<string name="diaconn_firmware_version">Version</string>
<string name="invalidpairing">Ugyldig parringsinformation. Anmoder om ny parring</string>
<string name="gettingpumpsettings">Henter pumpeindstillinger</string>
<string name="gettingpumptime">Henter pumpetid</string>
<string name="largetimediff">Stor tidsforskel:\nTid i pumpen varierer med mere end 1,5 time.\nJustér venligst tiden manuelt på pumpen og sørg for, at læsning af historikken fra pumpen ikke forårsager uventet adfærd.\nHvis det er muligt; fjern historik fra pumpen før du ændrer tiden eller deaktiverer closed loop i én DIA efter den sidste forkerte historik post, men minimum én DIA fra nu.</string>
<string name="largetimedifftitle">Stor tidsforskel</string>
<string name="approachingdailylimit">Nærmer sig daglig insulingrænse</string>
<string name="startingbolus">Starter bolus afgivelse</string>
<string name="waitingforestimatedbolusend">Venter på estimerede bolusafslutning</string>
<string name="gettingbolusstatus">Henter bolus status</string>
<string name="stoppingtempbasal">Stopper midlertidig basal</string>
<string name="settingtempbasal">Indstiller midlertidig basal</string>
<string name="settingextendedbolus">Indstiller forlænget bolus</string>
<string name="stoppingextendedbolus">Stopper forlænget bolus</string>
<string name="updatingbasalrates">Opdaterer basal rater</string>
<string name="description_pump_diaconn_g8">Pumpeintegration til Diaconn G8 Pumper</string>
<string name="diaconn_g8_pump">Diaconn G8</string>
<string name="diaconn_g8_pump_shortname">Diaconn G8</string>
<string name="maxbolusviolation">Max bolus overtrædelse</string>
<string name="commanderror">Kommando fejl</string>
<string name="speederror">Hastighedsfejl</string>
<string name="insulinlimitviolation">Overtrædelse af insulingrænse</string>
<string name="boluserrorcode">Ønsket: %1$.2fU Afgivet: %2$.2fU Fejlkode: %3$s</string>
<string name="diaconn_g8_valuenotsetproperly">Værdi ikke angivet korrekt</string>
<string name="diaconn_g8_bt_name_title">Diaconn G8 Bluetooth enhed</string>
<string name="diaconn_g8_password_title">Pumpe kodeord</string>
<string name="bolusspeed">Bolus hastighed</string>
<string name="selectedpump">Valgt pumpe</string>
<string name="diaconn_g8_useextended_title">Brug forlængede bolusser til &gt;200%%</string>
<string name="diaconn_g8_visualizeextendedaspercentage_title">Vis forlænget bolus som %%</string>
<string name="diaconn_g8_bluetooth_status">Bluetooth Status</string>
<string name="diagonn_g8_tdd_label">TDD</string>
<string name="bolus_step">Bolus Trin</string>
<string name="basal_step">Basal Trin</string>
<string name="pump_firmware_label">Firmware</string>
<string name="diagonn_g8_useroptions">BRUGERINDSTILLINGER</string>
<string name="pairfirst">Par venligst din pumpe med din telefon!</string>
<string name="processinghistory">"Behandler begivenhed "</string>
<string name="apslastLogNum">aps_last_log_num</string>
<string name="apsWrappingCount">aps_wrapping_count</string>
<string name="diaconn_g8_history_tempbasal">Midlertidig basal</string>
<string name="diaconng8_pump_settings">Diaconn pumpe indstillinger</string>
<string name="diaconn_g8_pumpalarm">Lyd</string>
<string name="diaconn_g8_pumpalarm_sound">lyd</string>
<string name="diaconn_g8_pumpalarm_vibrate">vibrér</string>
<string name="diaconn_g8_pumpalarm_silent">lydløs</string>
<string name="diaconn_g8_pumpalarmlevel">Alarm intensitet</string>
<string name="diaconn_g8_pumpalarm_intensity_low">lav</string>
<string name="diaconn_g8_pumpalarm_intensity_middle">middel</string>
<string name="diaconn_g8_pumpalarm_intensity_high">høj</string>
<string name="diaconn_g8_screentimeout">LCD tændt tid [second]</string>
<string name="diaconn_g8_saveuseroptions">GEM INDSTILLING TIL PUMPE</string>
<string name="diaconn_g8_language">Sprog</string>
<string name="diaconn_g8_bolus_speed">Bolus hastighed</string>
<string name="diaconn_g8_pumplang_chiness">Kinesisk</string>
<string name="diaconn_g8_pumplang_korean">Koreansk</string>
<string name="diaconn_g8_pumplang_english">Engelsk</string>
<string name="diaconn_g8_screentimeout_10">"10 "</string>
<string name="diaconn_g8_pumpscreentimeout_10">10</string>
<string name="diaconn_g8_pumpscreentimeout_20">20</string>
<string name="diaconn_g8_pumpscreentimeout_30">30</string>
<string name="diaconn_g8_pumpalarm_low">lav</string>
<string name="injectionblocked">Injektion blokkeret</string>
<string name="batterywarning">Batteri Advarsel</string>
<string name="insulinlackwarning">Lavt insulin niveau</string>
<string name="needbatteryreplace">Batteriudskiftning er påkrævet</string>
<string name="needinsullinreplace">Insulin udskiftning er påkrævet</string>
<string name="pumpversion">pump_version</string>
<string name="apsIncarnationNo">aps_incarnation_no</string>
<string name="pumpserialno">pump_serial_no</string>
<string name="diaconn_g8_loginsulinchange_title">Log reservoir skift</string>
<string name="diaconn_g8_loginsulinchange_summary">Tilføj \"Insulin Skift\" begivenhed til careportal, når de registreres i historik</string>
<string name="diaconn_g8_logcanulachange_title">Log indstik skift</string>
<string name="diaconn_g8_logcanulachange_summary">Tilføj \"Indstik skift\" begivenhed til careportal, når de registreres i historik</string>
<string name="diaconn_g8_logbatterychange_summary">Tilføj \"Batteri skift\" begivenhed til careportal, når det registreres i historik</string>
<string name="diaconn_g8_logbatterychange_title">Log skift af batteri</string>
<string name="diaconn_g8_logsyncinprogress">Synkronisering af log i gang</string>
<string name="diaconn_g8_loginsulinshorage">Lavt insulinniveau</string>
<string name="diaconn_g8_logbatteryshorage">Lavt batteriniveau</string>
<string name="diaconn_g8_logneedleprime">Nåle fyldning: %1$.2fIE</string>
<string name="diaconn_g8_loginjectorprime">Forfyldning: %1$.2fIE</string>
<string name="diaconn_g8_logtubeprime">Slange fyldning: %1$.2fIE</string>
<string name="diaconn_g8_resetfactoryreset">Nulstil efter fabriksnulstilling</string>
<string name="diaconn_g8_resetemergencyoff">Nulstil efter nødstilstand</string>
<string name="diaconn_g8_resetbatteryreplacement">Nulstil efter udskiftning af batteri</string>
<string name="diaconn_g8_resetaftercalibration">Nulstil efter kalibrering</string>
<string name="diaconn_g8_resetpreshipment">Nulstil til fabriksindstillinger</string>
<string name="diaconn_g8_resetunexpected">Uventet systemnulstilling</string>
<string name="diaconn_g8_reasoncomplete">Fuldført</string>
<string name="diaconn_g8_reasoninjectonblock">Injektion blokkeret</string>
<string name="diaconn_g8_reasonbatteryshortage">Lavt batteriniveau</string>
<string name="diaconn_g8_reasoninsulinshortage">Lavt insulinniveau</string>
<string name="diaconn_g8_reasonuserstop">Stoppet af bruger</string>
<string name="diaconn_g8_reasonsystemreset">Nulstil system</string>
<string name="diaconn_g8_reasonother">Andet</string>
<string name="diaconn_g8_reasonemergencystop">Nødstop</string>
<string name="diacon_g8_blockbasal">BASAL</string>
<string name="diacon_g8_blockmealbolus">MÅLTIDS BOLUS</string>
<string name="diacon_g8_blocknormalbolus">NORMAL BOLUS</string>
<string name="diacon_g8_blocksquarebolus">FORLÆNGET BOLUS</string>
<string name="diacon_g8_blockdualbolus">KOMBINERET BOLUS</string>
<string name="diacon_g8_blockreplacetube">UDSKIFT SLANGE</string>
<string name="diacon_g8_blockreplaceneedle">UDSKIFT KANYLE</string>
<string name="diacon_g8_blockreplacesyringe">UDSKIFT SPRØJTE</string>
<string name="diaconn_g8_logalarmblock">Injektion blokkeret (%s)</string>
<string name="diaconn_g8_lgorelease">basal afgivelse (%s)</string>
<string name="diaconn_g8_lgosuspend">basal suspendering (%s)</string>
<string name="diaconn_g8_logdualnormalsuccess">Normal kombinationsbolus vellykket</string>
<string name="diaconn_g8_logdualsquarestart">Kombineret bolus startet</string>
<string name="diaconn_g8_logdualsquaresuccess">Kombineret bolus vellykket</string>
<string name="diaconn_g8_logsquarestart">Forlænget bolus startet</string>
<string name="diaconn_g8_logsquaresuccess">Forlænget bolus vellykket</string>
<string name="diaconn_g8_logmealfail">Måltid Mislykkedes</string>
<string name="diaconn_g8_logsuccess">Succes</string>
<string name="diaconn_g8_logmealsuccess">Måltid Gennemført</string>
<string name="diaconn_g8_errorcode_1">Kan ikke kontrollere pga. pakke CRC fejl.</string>
<string name="diaconn_g8_errorcode_2">Kan ikke indstilles pga. en inputparameter fejl.</string>
<string name="diaconn_g8_errorcode_3">Kan ikke indstilles pga. en protokol specifikationsfejl.</string>
<string name="diaconn_g8_errorcode_4">Måltid registreret, kan ikke afgive.</string>
<string name="diaconn_g8_errorcode_6">Annulleret af pumpen</string>
<string name="diaconn_g8_errorcode_7">Tager andre handlinger, begrænser app-indstillinger.</string>
<string name="diaconn_g8_errorcode_8">Under en anden Bolus-injektion er injektioner begrænsede.</string>
<string name="diaconn_g8_errorcode_9">Kræver genoptagelse af basal afgivelse</string>
<string name="diaconn_g8_errorcode_10">Annulleret pga. manglende respons på pumpen.</string>
<string name="diaconn_g8_errorcode_11">Injektion er ikke mulig pga. lavt batteri.</string>
<string name="diaconn_g8_errorcode_12">Insulinmangel, kan ikke afgives.</string>
<string name="diaconn_g8_errorcode_13">Du kan ikke afgive, da det overskrider grænsen.</string>
<string name="diaconn_g8_errorcode_14">Injektionen kan ikke afgives, da den overstiger max. daglig dosis.</string>
<string name="diaconn_g8_errorcode_15">Efter basal opsætning er færdig, kan basal injektion udføres.</string>
<string name="diaconn_g8_errotpreceivedyet">Kommandoen blev ikke leveret. Prøv igen.</string>
<string name="diaconn_g8_logtubechange_title">Log skift af slange</string>
<string name="diaconn_g8_logtubechange_summary">Tilføj \"Slange skift\" begivenhed til careportal, når de registreres i historik</string>
<string name="diaconn_g8_logtempstart">Midlertidig basal start</string>
<string name="diaconn_g8_errorcode_32">Under LGS er injektionen begrænset</string>
<string name="diaconn_g8_errorcode_33">LGS-status er allerede TIL, kommando afvist.</string>
<string name="diaconn_g8_errorcode_34">LGS-status er allerede FRA, kommando afvist.</string>
<string name="diaconn_g8_errorcode_35">Midlertidig basal kan ikke startes fordi en anden midlertidig basal er i gang</string>
<string name="diaconn_g8_errorcode_36">Midlertidig basal kan ikke stoppes fordi en midlertidig basal ikke er i gang</string>
<string name="diaconn_g8_cloudsend_summary">Send pumpelogs til Diaconn Cloud.</string>
<string name="diaconn_g8_cloudsend_title">Diaconn Cloud Synkronisering</string>
</resources>

View file

@ -152,4 +152,6 @@
<string name="diaconn_g8_errorcode_34">LGS status er AV. AV kommandoen er avslått.</string>
<string name="diaconn_g8_errorcode_35">Temp basal avslått siden det allerede kjøres en temp basal</string>
<string name="diaconn_g8_errorcode_36">Stopp av temp basal avslått siden det ikke kjøres en temp basal</string>
<string name="diaconn_g8_cloudsend_summary">Send pumpe logger til Diaconn Cloud.</string>
<string name="diaconn_g8_cloudsend_title">Diaconn Cloud Sync</string>
</resources>

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl = https\://services.gradle.org/distributions/gradle-7.2-all.zip
distributionUrl = https\://services.gradle.org/distributions/gradle-7.3.3-all.zip

View file

@ -91,4 +91,6 @@
<string name="set_neutral_temps_summary">Hvis aktiveret, vil det annullere en midlertidig basal inden udgangen af hver time. Denne metode kan hjælpe med at stoppe nogle pumper i at bippe/vibrere hver time.</string>
<string name="mdt_tbr_remaining">%1$.1f E/t (%2$d min. tilbage)</string>
<string name="invalid_history_data">Ugyldig pumpehistorikdata registreret. Åbn nyt problem og upload logfiler.</string>
<string name="riley_statistics">RL Statistik</string>
<string name="medtronic_history_type">Type:</string>
</resources>

View file

@ -77,4 +77,5 @@
</plurals>
<string name="orange_use_scanning_level">Brug Scanning</string>
<string name="orange_use_scanning_level_summary">Scan før du opretter forbindelse til OrangeLink, det bør forbedre forbindelsen (kan også bruges med andre RileyLink kloner, hvis nødvendigt)</string>
<string name="rileylink_configuration">RileyLink Konfiguration</string>
</resources>

View file

@ -5,6 +5,7 @@ enum class LTag(val tag: String, val defaultValue : Boolean = true, val requires
APS("APS"),
AUTOSENS("AUTOSENS", defaultValue = false),
AUTOMATION("AUTOMATION"),
AUTOTUNE("AUTOTUNE", defaultValue = false),
BGSOURCE("BGSOURCE"),
CONFIGBUILDER("CONFIGBUILDER"),
CONSTRAINTS("CONSTRAINTS"),

View file

@ -2,6 +2,18 @@
<resources>
<string name="app_name">AAPS</string>
<string name="label_actions_activity">AAPS</string>
<string name="label_watchface">AAPS</string>
<string name="label_watchface_large">AAPS (Stor)</string>
<string name="label_watchface_big_chart">AAPS(StorGraf)</string>
<string name="label_watchface_no_chart">AAPS(IngenGraf)</string>
<string name="label_watchface_circle">AAPS(Cirkel)</string>
<string name="label_watchface_v2">AAPS(v2)</string>
<string name="label_watchface_cockpit">AAPS(Cockpit)</string>
<string name="label_watchface_steampunk">AAPS(Steampunk)</string>
<string name="label_watchface_digital_style">AAPS (DigitalStil)</string>
<string name="label_actions_tile">AAPS (Handlinger)</string>
<string name="label_temp_target_tile">AAPS(Midl. Mål)</string>
<string name="label_quick_wizard_tile">AAPS(Hurtig Guide)</string>
<string name="label_warning_sync">Ingen data!</string>
<string name="label_warning_old">Gammel data!</string>
<string name="label_warning_since">Siden %1$s</string>
@ -45,14 +57,23 @@
<string name="pref_ring_history">Ring Historik</string>
<string name="pref_light_ring_history">Lys Ring Historik</string>
<string name="pref_animations">Animationer</string>
<string name="pref_wizard_in_menu">Beregner i Menu</string>
<string name="pref_prime_in_menu">Prime i menuen</string>
<string name="pref_single_target">Enkelt Mål</string>
<string name="pref_wizard_percentage">Beregner Procent</string>
<string name="pref_complication_tap_action">Complication handling ved tryk</string>
<string name="pref_unicode_in_complications">Unicode i komplikationer</string>
<string name="pref_version">Version:</string>
<string name="pref_moreWatchfaceSettings">Flere urskive indstillinger</string>
<string name="pref_lookInYourWatchfaceConfiguration">Se venligst på Watchface-konfigurationen.</string>
<string name="menu_tempt">MidlertidigMål</string>
<string name="menu_wizard">Beregner</string>
<string name="menu_wizard_short">Beregn</string>
<string name="menu_treatment">Behandling</string>
<string name="menu_treatment_short">Behandl</string>
<string name="menu_bolus">Bolus</string>
<string name="menu_carb">Kulhydrater</string>
<string name="menu_ecarb">Forlængede kulhydrater</string>
<string name="menu_settings">Indstillinger</string>
<string name="menu_status">Status</string>
<string name="menu_resync">Gensynkronisering</string>
@ -60,17 +81,38 @@
<string name="menu_none">Ingen</string>
<string name="menu_default">Standard</string>
<string name="menu_menu">Menu</string>
<string name="quick_wizard_short">XL</string>
<string name="action_duration">Varighed</string>
<string name="action_tempt_confirmation">Midlertidig Mål Anmodet</string>
<string name="action_quick_wizard_confirmation">Hurtig Guide Anmodet</string>
<string name="action_treatment_confirmation">Behandling Anmodet</string>
<string name="action_bolus_confirmation">Bolus Anmodet</string>
<string name="action_wizard_confirmation">Beregning Anmodet</string>
<string name="action_fill_confirmation">Fyldning Anmodet</string>
<string name="action_ecarb_confirmation">Kulhydrater Anmodet</string>
<string name="action_profile_switch_confirmation">Profilskift anmodet</string>
<string name="action_target" comment="In temp target menu, single target value">Mål</string>
<string name="action_low" comment="In temp target menu, lower value from range">Lav</string>
<string name="action_high" comment="In temp target menu, higher value from range">Høj</string>
<string name="action_carbs">Kulhydrater</string>
<string name="action_ecarbs">Forlængede kulhydrater</string>
<string name="action_percentage">Procentdel</string>
<string name="action_start_min">Start [min]</string>
<string name="action_duration_h">Varighed [h]</string>
<string name="action_insulin">Insulin</string>
<string name="action_preset_1">Forvalg 1</string>
<string name="action_preset_2">Forvalg 2</string>
<string name="action_preset_3">Forvalg 3</string>
<string name="action_free_amount" comment="In prime/fill menu, allows to enter any amount to be used for priming/filling">Fri mængde</string>
<string name="action_confirm">BEKRÆFT</string>
<string name="action_timeshift">tidsforskydning</string>
<string name="action_bolus">Bolus</string>
<string name="bolus_progress">Bolus Fremskridt</string>
<string name="press_to_cancel">tryk for at annullere</string>
<string name="cancel_bolus">ANNULLER BOLUS</string>
<string name="status_pump">Pumpe</string>
<string name="status_loop">Loop</string>
<string name="status_profile_switch">Profilskift</string>
<string name="status_tdd">TDD</string>
<string name="activity_carb">Kulhydrat</string>
<string name="activity_IOB">IOB</string>
@ -117,4 +159,13 @@
<string name="simple_ui_charging">Under Opladning</string>
<string name="simple_ui_always_on">Altid Tændt Tilstand</string>
<string name="simple_ui_always_on_charging">Altid Tændt ved opladning</string>
<string name="temp_target_eating_soon">Spiser</string>
<string name="temp_target_hypo">Hypo</string>
<string name="temp_target_activity">Aktivitet</string>
<string name="temp_target_manual">Manuel</string>
<string name="temp_target_cancel">Annuller</string>
<string name="tile_none">Ingen</string>
<string name="tile_no_config">Ingen konfiguration tilgængelig</string>
<string name="wear_control_not_enabled">Wear kontrol deaktiveret</string>
<string name="wear_control_no_data">Ingen data tilgængelig</string>
</resources>

View file

@ -2,6 +2,15 @@
<resources>
<string name="app_name">AAPS</string>
<string name="label_actions_activity">AAPS</string>
<string name="label_watchface">AAPS</string>
<string name="label_watchface_large">AAPS (stor)</string>
<string name="label_watchface_big_chart">AAPS (stor graf)</string>
<string name="label_watchface_no_chart">AAPS (ingen graf)</string>
<string name="label_watchface_circle">AAPS (sirkel)</string>
<string name="label_watchface_v2">AAPS(v2)</string>
<string name="label_watchface_cockpit">AAPS(Cockpit)</string>
<string name="label_watchface_steampunk">AAPS(Steampunk)</string>
<string name="label_watchface_digital_style">AAPS(Digitalstil)</string>
<string name="label_actions_tile">AAPS(Actions)</string>
<string name="label_temp_target_tile">AAPS(Temp Target)</string>
<string name="label_quick_wizard_tile">AAPS(Quick Wizard)</string>
@ -55,6 +64,7 @@
<string name="pref_complication_tap_action">Komplikasjon trykk handling</string>
<string name="pref_unicode_in_complications">Unicode ikomplikasjoner</string>
<string name="pref_version">Versjon:</string>
<string name="pref_moreWatchfaceSettings">Fler urskive innstillinger</string>
<string name="pref_lookInYourWatchfaceConfiguration">Vennligst sjekk urskive innstillinger.</string>
<string name="menu_tempt">TempT</string>
<string name="menu_wizard">Kalkulator</string>
@ -80,6 +90,7 @@
<string name="action_wizard_confirmation">Beregning forespurt</string>
<string name="action_fill_confirmation">Fylling forespurt</string>
<string name="action_ecarb_confirmation">Karbo forespurt</string>
<string name="action_profile_switch_confirmation">Profilbytte forespurt</string>
<string name="action_target" comment="In temp target menu, single target value">Mål</string>
<string name="action_low" comment="In temp target menu, lower value from range">Lav</string>
<string name="action_high" comment="In temp target menu, higher value from range">Høy</string>
@ -101,6 +112,7 @@
<string name="cancel_bolus">AVBRYT BOLUS</string>
<string name="status_pump">Pumpe</string>
<string name="status_loop">Loop</string>
<string name="status_profile_switch">Profilbytte</string>
<string name="status_tdd">TDD</string>
<string name="activity_carb">Karbo</string>
<string name="activity_IOB">IOB</string>