diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt index 5556ea5323..de1c931ccb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt @@ -32,11 +32,9 @@ import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.sharedPreferences.SP import io.reactivex.disposables.CompositeDisposable import org.json.JSONArray -import java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton -import kotlin.collections.ArrayList import kotlin.math.abs import kotlin.math.floor import kotlin.math.max @@ -90,7 +88,7 @@ open class IobCobCalculatorPlugin @Inject constructor( disposable.add(rxBus .toObservable(EventConfigBuilderChange::class.java) .observeOn(aapsSchedulers.io) - .subscribe({ event: EventConfigBuilderChange? -> + .subscribe({ event -> stopCalculation("onEventConfigBuilderChange") synchronized(dataLock) { aapsLogger.debug(LTag.AUTOSENS, "Invalidating cached data because of configuration change.") @@ -103,10 +101,7 @@ open class IobCobCalculatorPlugin @Inject constructor( disposable.add(rxBus .toObservable(EventNewBasalProfile::class.java) .observeOn(aapsSchedulers.io) - .subscribe({ event: EventNewBasalProfile? -> - if (event == null) { // on init no need of reset - return@subscribe - } + .subscribe({ event -> stopCalculation("onNewProfile") synchronized(dataLock) { aapsLogger.debug(LTag.AUTOSENS, "Invalidating cached data because of new profile.") @@ -120,7 +115,7 @@ open class IobCobCalculatorPlugin @Inject constructor( .toObservable(EventNewBG::class.java) .observeOn(aapsSchedulers.io) .debounce(1L, TimeUnit.SECONDS) - .subscribe({ event: EventNewBG? -> + .subscribe({ event -> stopCalculation("onEventNewBG") runCalculation("onEventNewBG", System.currentTimeMillis(), bgDataReload = true, limitDataToOldestAvailable = true, cause = event) }, fabricPrivacy::logException) @@ -129,7 +124,7 @@ open class IobCobCalculatorPlugin @Inject constructor( disposable.add(rxBus .toObservable(EventPreferenceChange::class.java) .observeOn(aapsSchedulers.io) - .subscribe({ event: EventPreferenceChange -> + .subscribe({ event -> if (event.isChanged(resourceHelper, R.string.key_openapsama_autosens_period) || event.isChanged(resourceHelper, R.string.key_age) || event.isChanged(resourceHelper, R.string.key_absorption_maxtime) || @@ -151,19 +146,19 @@ open class IobCobCalculatorPlugin @Inject constructor( disposable.add(rxBus .toObservable(EventAppInitialized::class.java) .observeOn(aapsSchedulers.io) - .subscribe({ event: EventAppInitialized? -> runCalculation("onEventAppInitialized", System.currentTimeMillis(), bgDataReload = true, limitDataToOldestAvailable = true, cause = event) }, fabricPrivacy::logException) + .subscribe({ event -> runCalculation("onEventAppInitialized", System.currentTimeMillis(), bgDataReload = true, limitDataToOldestAvailable = true, cause = event) }, fabricPrivacy::logException) ) // EventNewHistoryData disposable.add(rxBus .toObservable(EventNewHistoryData::class.java) .observeOn(aapsSchedulers.io) - .subscribe({ event: EventNewHistoryData -> newHistoryData(event, false) }, fabricPrivacy::logException) + .subscribe({ event -> newHistoryData(event, false) }, fabricPrivacy::logException) ) // EventNewHistoryBgData disposable.add(rxBus .toObservable(EventNewHistoryBgData::class.java) .observeOn(aapsSchedulers.io) - .subscribe({ event: EventNewHistoryBgData -> newHistoryData(EventNewHistoryData(event.timestamp), true) }, fabricPrivacy::logException) + .subscribe({ event -> newHistoryData(EventNewHistoryData(event.timestamp), true) }, fabricPrivacy::logException) ) } @@ -691,7 +686,7 @@ open class IobCobCalculatorPlugin @Inject constructor( } } - fun runCalculation(from: String, end: Long, bgDataReload: Boolean, limitDataToOldestAvailable: Boolean, cause: Event?) { + fun runCalculation(from: String, end: Long, bgDataReload: Boolean, limitDataToOldestAvailable: Boolean, cause: Event) { aapsLogger.debug(LTag.AUTOSENS, "Starting calculation thread: " + from + " to " + dateUtil.dateAndTimeAndSecondsString(end)) if (thread == null || thread?.state == Thread.State.TERMINATED) { thread = if (sensitivityOref1Plugin.isEnabled()) IobCobOref1Thread(injector, this, treatmentsPlugin, from, end, bgDataReload, limitDataToOldestAvailable, cause) else IobCobThread(injector, this, treatmentsPlugin, from, end, bgDataReload, limitDataToOldestAvailable, cause) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java deleted file mode 100644 index 21a6868bfd..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java +++ /dev/null @@ -1,399 +0,0 @@ -package info.nightscout.androidaps.plugins.iob.iobCobCalculator; - -import android.content.Context; -import android.os.PowerManager; -import android.os.SystemClock; - -import androidx.collection.LongSparseArray; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.List; - -import javax.inject.Inject; - -import dagger.android.HasAndroidInjector; -import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.IobTotal; -import info.nightscout.androidaps.data.Profile; -import info.nightscout.androidaps.db.TempTarget; -import info.nightscout.androidaps.db.Treatment; -import info.nightscout.androidaps.events.Event; -import info.nightscout.androidaps.interfaces.ProfileFunction; -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults; -import info.nightscout.androidaps.plugins.bus.RxBusWrapper; -import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; -import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensBgLoaded; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.DecimalFormatter; -import info.nightscout.androidaps.utils.FabricPrivacy; -import info.nightscout.androidaps.utils.MidnightTime; -import info.nightscout.androidaps.utils.Profiler; -import info.nightscout.androidaps.utils.T; -import info.nightscout.androidaps.utils.buildHelper.BuildHelper; -import info.nightscout.androidaps.utils.resources.ResourceHelper; -import info.nightscout.androidaps.utils.sharedPreferences.SP; - -import static info.nightscout.androidaps.utils.DateUtil.now; -import static java.util.Calendar.MINUTE; - -/** - * Created by mike on 23.01.2018. - */ - -public class IobCobOref1Thread extends Thread { - private final Event cause; - - @Inject AAPSLogger aapsLogger; - @Inject SP sp; - @Inject RxBusWrapper rxBus; - @Inject ResourceHelper resourceHelper; - @Inject ProfileFunction profileFunction; - @Inject Context context; - @Inject SensitivityAAPSPlugin sensitivityAAPSPlugin; - @Inject SensitivityWeightedAveragePlugin sensitivityWeightedAveragePlugin; - @Inject BuildHelper buildHelper; - @Inject Profiler profiler; - @Inject FabricPrivacy fabricPrivacy; - @Inject DateUtil dateUtil; - - private final HasAndroidInjector injector; - private final IobCobCalculatorPlugin iobCobCalculatorPlugin; // cannot be injected : HistoryBrowser uses different instance - private final TreatmentsPlugin treatmentsPlugin; // cannot be injected : HistoryBrowser uses different instance - private final boolean bgDataReload; - private final boolean limitDataToOldestAvailable; - private final String from; - private final long end; - - private PowerManager.WakeLock mWakeLock; - - IobCobOref1Thread(HasAndroidInjector injector, IobCobCalculatorPlugin iobCobCalculatorPlugin, TreatmentsPlugin treatmentsPlugin, String from, long end, boolean bgDataReload, boolean limitDataToOldestAvailable, Event cause) { - super(); - injector.androidInjector().inject(this); - this.injector = injector; - this.iobCobCalculatorPlugin = iobCobCalculatorPlugin; - this.treatmentsPlugin = treatmentsPlugin; - - this.bgDataReload = bgDataReload; - this.limitDataToOldestAvailable = limitDataToOldestAvailable; - this.from = from; - this.cause = cause; - this.end = end; - - PowerManager powerManager = (PowerManager) context.getApplicationContext().getSystemService(Context.POWER_SERVICE); - if (powerManager != null) - mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, resourceHelper.gs(R.string.app_name) + ":iobCobThread"); - } - - @Override - public final void run() { - long start = DateUtil.now(); - if (mWakeLock != null) - mWakeLock.acquire(T.mins(10).msecs()); - try { - aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread started: " + from); - if (!profileFunction.isProfileValid("IobCobThread")) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No profile): " + from); - return; // app still initializing - } - //log.debug("Locking calculateSensitivityData"); - - long oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable); - - synchronized (iobCobCalculatorPlugin.getDataLock()) { - if (bgDataReload) { - iobCobCalculatorPlugin.loadBgData(end); - iobCobCalculatorPlugin.createBucketedData(); - rxBus.send(new EventAutosensBgLoaded(cause)); - } - List bucketed_data = iobCobCalculatorPlugin.getBucketedData(); - LongSparseArray autosensDataTable = iobCobCalculatorPlugin.getAutosensDataTable(); - - if (bucketed_data == null || bucketed_data.size() < 3) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): " + from); - return; - } - - long prevDataTime = IobCobCalculatorPlugin.Companion.roundUpTime(bucketed_data.get(bucketed_data.size() - 3).getTimestamp()); - aapsLogger.debug(LTag.AUTOSENS, "Prev data time: " + dateUtil.dateAndTimeString(prevDataTime)); - AutosensData previous = autosensDataTable.get(prevDataTime); - // start from oldest to be able sub cob - for (int i = bucketed_data.size() - 4; i >= 0; i--) { - String progress = i + (buildHelper.isDev() ? " (" + from + ")" : ""); - rxBus.send(new EventIobCalculationProgress(progress)); - - if (iobCobCalculatorPlugin.getStopCalculationTrigger()) { - iobCobCalculatorPlugin.setStopCalculationTrigger(false); - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): " + from); - return; - } - // check if data already exists - long bgTime = bucketed_data.get(i).getTimestamp(); - bgTime = IobCobCalculatorPlugin.Companion.roundUpTime(bgTime); - if (bgTime > IobCobCalculatorPlugin.Companion.roundUpTime(now())) - continue; - - AutosensData existing; - if ((existing = autosensDataTable.get(bgTime)) != null) { - previous = existing; - continue; - } - - Profile profile = profileFunction.getProfile(bgTime); - if (profile == null) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): " + from); - return; // profile not set yet - } - - aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: " + from + " (" + i + "/" + bucketed_data.size() + ")"); - - double sens = profile.getIsfMgdl(bgTime); - - AutosensData autosensData = new AutosensData(injector); - autosensData.time = bgTime; - if (previous != null) - autosensData.activeCarbsList = previous.cloneCarbsList(); - else - autosensData.activeCarbsList = new ArrayList<>(); - - //console.error(bgTime , bucketed_data[i].glucose); - double bg; - double avgDelta; - double delta; - bg = bucketed_data.get(i).getValue(); - if (bg < 39 || bucketed_data.get(i + 3).getValue() < 39) { - aapsLogger.error("! value < 39"); - continue; - } - autosensData.bg = bg; - delta = (bg - bucketed_data.get(i + 1).getValue()); - avgDelta = (bg - bucketed_data.get(i + 3).getValue()) / 3; - - IobTotal iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile); - - double bgi = -iob.activity * sens * 5; - double deviation = delta - bgi; - double avgDeviation = Math.round((avgDelta - bgi) * 1000) / 1000d; - - double slopeFromMaxDeviation = 0; - double slopeFromMinDeviation = 999; - double maxDeviation = 0; - double minDeviation = 999; - - // https://github.com/openaps/oref0/blob/master/lib/determine-basal/cob-autosens.js#L169 - if (i < bucketed_data.size() - 16) { // we need 1h of data to calculate minDeviationSlope - long hourago = bgTime + 10 * 1000 - 60 * 60 * 1000L; - AutosensData hourAgoData = iobCobCalculatorPlugin.getAutosensData(hourago); - if (hourAgoData != null) { - int initialIndex = autosensDataTable.indexOfKey(hourAgoData.time); - aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketed_data.size() + " i=" + i + " hourAgoData=" + hourAgoData.toString()); - int past = 1; - try { - for (; past < 12; past++) { - AutosensData ad = autosensDataTable.valueAt(initialIndex + past); - aapsLogger.debug(LTag.AUTOSENS, ">>>>> past=" + past + " ad=" + (ad != null ? ad.toString() : null)); - if (ad == null) { - aapsLogger.debug(LTag.AUTOSENS, autosensDataTable.toString()); - aapsLogger.debug(LTag.AUTOSENS, bucketed_data.toString()); - aapsLogger.debug(LTag.AUTOSENS, iobCobCalculatorPlugin.getBgReadings().toString()); - Notification notification = new Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW); - rxBus.send(new EventNewNotification(notification)); - sp.putBoolean("log_AUTOSENS", true); - break; - } - // let it here crash on NPE to get more data as i cannot reproduce this bug - double deviationSlope = (ad.avgDeviation - avgDeviation) / (ad.time - bgTime) * 1000 * 60 * 5; - if (ad.avgDeviation > maxDeviation) { - slopeFromMaxDeviation = Math.min(0, deviationSlope); - maxDeviation = ad.avgDeviation; - } - if (ad.avgDeviation < minDeviation) { - slopeFromMinDeviation = Math.max(0, deviationSlope); - minDeviation = ad.avgDeviation; - } - - //if (Config.isEnabled(L.AUTOSENS)) - // log.debug("Deviations: " + new Date(bgTime) + new Date(ad.time) + " avgDeviation=" + avgDeviation + " deviationSlope=" + deviationSlope + " slopeFromMaxDeviation=" + slopeFromMaxDeviation + " slopeFromMinDeviation=" + slopeFromMinDeviation); - } - } catch (Exception e) { - aapsLogger.error("Unhandled exception", e); - fabricPrivacy.logException(e); - aapsLogger.debug(autosensDataTable.toString()); - aapsLogger.debug(bucketed_data.toString()); - aapsLogger.debug(iobCobCalculatorPlugin.getBgReadings().toString()); - Notification notification = new Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW); - rxBus.send(new EventNewNotification(notification)); - sp.putBoolean("log_AUTOSENS", true); - break; - } - } else { - aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketed_data.size() + " i=" + i + " hourAgoData=" + "null"); - } - } - - List recentCarbTreatments = treatmentsPlugin.getCarbTreatments5MinBackFromHistory(bgTime); - for (Treatment recentCarbTreatment : recentCarbTreatments) { - autosensData.carbsFromBolus += recentCarbTreatment.carbs; - boolean isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled(); - autosensData.activeCarbsList.add(autosensData.new CarbsInPast(recentCarbTreatment, isAAPSOrWeighted)); - autosensData.pastSensitivity += "[" + DecimalFormatter.to0Decimal(recentCarbTreatment.carbs) + "g]"; - } - - - // if we are absorbing carbs - if (previous != null && previous.cob > 0) { - // calculate sum of min carb impact from all active treatments - double totalMinCarbsImpact = 0d; -// if (SensitivityAAPSPlugin.getPlugin().isEnabled(PluginType.SENSITIVITY) || SensitivityWeightedAveragePlugin.getPlugin().isEnabled(PluginType.SENSITIVITY)) { - //when the impact depends on a max time, sum them up as smaller carb sizes make them smaller -// for (int ii = 0; ii < autosensData.activeCarbsList.size(); ++ii) { -// AutosensData.CarbsInPast c = autosensData.activeCarbsList.get(ii); -// totalMinCarbsImpact += c.min5minCarbImpact; -// } -// } else { - //Oref sensitivity - totalMinCarbsImpact = sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact); -// } - - // figure out how many carbs that represents - // but always assume at least 3mg/dL/5m (default) absorption per active treatment - double ci = Math.max(deviation, totalMinCarbsImpact); - if (ci != deviation) - autosensData.failoverToMinAbsorbtionRate = true; - autosensData.absorbed = ci * profile.getIc(bgTime) / sens; - // and add that to the running total carbsAbsorbed - autosensData.cob = Math.max(previous.cob - autosensData.absorbed, 0d); - autosensData.mealCarbs = previous.mealCarbs; - autosensData.substractAbosorbedCarbs(); - autosensData.usedMinCarbsImpact = totalMinCarbsImpact; - autosensData.absorbing = previous.absorbing; - autosensData.mealStartCounter = previous.mealStartCounter; - autosensData.type = previous.type; - autosensData.uam = previous.uam; - } - - boolean isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled(); - autosensData.removeOldCarbs(bgTime, isAAPSOrWeighted); - autosensData.cob += autosensData.carbsFromBolus; - autosensData.mealCarbs += autosensData.carbsFromBolus; - autosensData.deviation = deviation; - autosensData.bgi = bgi; - autosensData.delta = delta; - autosensData.avgDelta = avgDelta; - autosensData.avgDeviation = avgDeviation; - autosensData.slopeFromMaxDeviation = slopeFromMaxDeviation; - autosensData.slopeFromMinDeviation = slopeFromMinDeviation; - - - // If mealCOB is zero but all deviations since hitting COB=0 are positive, exclude from autosens - if (autosensData.cob > 0 || autosensData.absorbing || autosensData.mealCarbs > 0) { - autosensData.absorbing = deviation > 0; - // stop excluding positive deviations as soon as mealCOB=0 if meal has been absorbing for >5h - if (autosensData.mealStartCounter > 60 && autosensData.cob < 0.5) { - autosensData.absorbing = false; - } - if (!autosensData.absorbing && autosensData.cob < 0.5) { - autosensData.mealCarbs = 0; - } - // check previous "type" value, and if it wasn't csf, set a mealAbsorption start flag - if (!autosensData.type.equals("csf")) { -// process.stderr.write("("); - autosensData.mealStartCounter = 0; - } - autosensData.mealStartCounter++; - autosensData.type = "csf"; - } else { - // check previous "type" value, and if it was csf, set a mealAbsorption end flag - if (autosensData.type.equals("csf")) { -// process.stderr.write(")"); - } - - double currentBasal = profile.getBasal(bgTime); - // always exclude the first 45m after each carb entry - //if (iob.iob > currentBasal || uam ) { - if (iob.iob > 2 * currentBasal || autosensData.uam || autosensData.mealStartCounter < 9) { - autosensData.mealStartCounter++; - autosensData.uam = deviation > 0; - if (!autosensData.type.equals("uam")) { -// process.stderr.write("u("); - } - autosensData.type = "uam"; - } else { - if (autosensData.type.equals("uam")) { -// process.stderr.write(")"); - } - autosensData.type = "non-meal"; - } - } - - // Exclude meal-related deviations (carb absorption) from autosens - if (autosensData.type.equals("non-meal")) { - if (Math.abs(deviation) < Constants.DEVIATION_TO_BE_EQUAL) { - autosensData.pastSensitivity += "="; - autosensData.validDeviation = true; - } else if (deviation > 0) { - autosensData.pastSensitivity += "+"; - autosensData.validDeviation = true; - } else { - autosensData.pastSensitivity += "-"; - autosensData.validDeviation = true; - } - } else if (autosensData.type.equals("uam")) { - autosensData.pastSensitivity += "u"; - } else { - autosensData.pastSensitivity += "x"; - } - //log.debug("TIME: " + new Date(bgTime).toString() + " BG: " + bg + " SENS: " + sens + " DELTA: " + delta + " AVGDELTA: " + avgDelta + " IOB: " + iob.iob + " ACTIVITY: " + iob.activity + " BGI: " + bgi + " DEVIATION: " + deviation); - - // add an extra negative deviation if a high temptarget is running and exercise mode is set - // TODO AS-FIX - if (false && sp.getBoolean(R.string.key_high_temptarget_raises_sensitivity, SMBDefaults.high_temptarget_raises_sensitivity)) { - TempTarget tempTarget = treatmentsPlugin.getTempTargetFromHistory(bgTime); - if (tempTarget != null && tempTarget.target() >= 100) { - autosensData.extraDeviation.add(-(tempTarget.target() - 100) / 20); - } - } - - // add one neutral deviation every 2 hours to help decay over long exclusion periods - GregorianCalendar calendar = new GregorianCalendar(); - calendar.setTimeInMillis(bgTime); - int min = calendar.get(MINUTE); - int hours = calendar.get(Calendar.HOUR_OF_DAY); - if (min >= 0 && min < 5 && hours % 2 == 0) - autosensData.extraDeviation.add(0d); - - previous = autosensData; - if (bgTime < now()) - autosensDataTable.put(bgTime, autosensData); - aapsLogger.debug(LTag.AUTOSENS, "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + iobCobCalculatorPlugin.lastDataTime()); - AutosensResult sensitivity = iobCobCalculatorPlugin.detectSensitivityWithLock(oldestTimeWithData, bgTime); - aapsLogger.debug(LTag.AUTOSENS, "Sensitivity result: " + sensitivity.toString()); - autosensData.autosensResult = sensitivity; - aapsLogger.debug(LTag.AUTOSENS, autosensData.toString()); - } - } - new Thread(() -> { - SystemClock.sleep(1000); - rxBus.send(new EventAutosensCalculationFinished(cause)); - }).start(); - } finally { - if (mWakeLock != null) - mWakeLock.release(); - rxBus.send(new EventIobCalculationProgress("")); - aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: " + from); - aapsLogger.debug(LTag.AUTOSENS, "Midnights: " + MidnightTime.log()); - profiler.log(LTag.AUTOSENS, "IobCobOref1Thread", start); - } - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt new file mode 100644 index 0000000000..5bb64109ae --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt @@ -0,0 +1,328 @@ +package info.nightscout.androidaps.plugins.iob.iobCobCalculator + +import android.content.Context +import android.os.PowerManager +import android.os.SystemClock +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.R +import info.nightscout.androidaps.events.Event +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin.Companion.roundUpTime +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensBgLoaded +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress +import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin +import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.utils.* +import info.nightscout.androidaps.utils.buildHelper.BuildHelper +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP +import java.util.* +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min +import kotlin.math.roundToLong + +class IobCobOref1Thread internal constructor( + private val injector: HasAndroidInjector, + private val iobCobCalculatorPlugin: IobCobCalculatorPlugin, // cannot be injected : HistoryBrowser uses different instance + private val treatmentsPlugin: TreatmentsPlugin, // cannot be injected : HistoryBrowser uses different instance + private val from: String, + private val end: Long, + private val bgDataReload: Boolean, + private val limitDataToOldestAvailable: Boolean, + private val cause: Event +) : Thread() { + + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var sp: SP + @Inject lateinit var rxBus: RxBusWrapper + @Inject lateinit var resourceHelper: ResourceHelper + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var context: Context + @Inject lateinit var sensitivityAAPSPlugin: SensitivityAAPSPlugin + @Inject lateinit var sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin + @Inject lateinit var buildHelper: BuildHelper + @Inject lateinit var profiler: Profiler + @Inject lateinit var fabricPrivacy: FabricPrivacy + @Inject lateinit var dateUtil: DateUtil + + private var mWakeLock: PowerManager.WakeLock? = null + + init { + injector.androidInjector().inject(this) + mWakeLock = (context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, resourceHelper.gs(R.string.app_name) + ":iobCobThread") + } + + override fun run() { + val start = DateUtil.now() + mWakeLock?.acquire(T.mins(10).msecs()) + try { + aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread started: $from") + if (!profileFunction.isProfileValid("IobCobThread")) { + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No profile): $from") + return // app still initializing + } + //log.debug("Locking calculateSensitivityData"); + val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable) + synchronized(iobCobCalculatorPlugin.dataLock) { + if (bgDataReload) { + iobCobCalculatorPlugin.loadBgData(end) + iobCobCalculatorPlugin.createBucketedData() + rxBus.send(EventAutosensBgLoaded(cause)) + } + val bucketedData = iobCobCalculatorPlugin.bucketedData + val autosensDataTable = iobCobCalculatorPlugin.autosensDataTable + if (bucketedData == null || bucketedData.size < 3) { + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): $from") + return + } + val prevDataTime = roundUpTime(bucketedData[bucketedData.size - 3].timestamp) + aapsLogger.debug(LTag.AUTOSENS, "Prev data time: " + dateUtil.dateAndTimeString(prevDataTime)) + var previous = autosensDataTable[prevDataTime] + // start from oldest to be able sub cob + for (i in bucketedData.size - 4 downTo 0) { + val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else "" + rxBus.send(EventIobCalculationProgress(progress)) + if (iobCobCalculatorPlugin.stopCalculationTrigger) { + iobCobCalculatorPlugin.stopCalculationTrigger = false + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from") + return + } + // check if data already exists + var bgTime = bucketedData[i].timestamp + bgTime = roundUpTime(bgTime) + if (bgTime > roundUpTime(DateUtil.now())) continue + var existing: AutosensData? + if (autosensDataTable[bgTime].also { existing = it } != null) { + previous = existing + continue + } + val profile = profileFunction.getProfile(bgTime) + if (profile == null) { + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): $from") + return // profile not set yet + } + aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: " + from + " (" + i + "/" + bucketedData.size + ")") + val sens = profile.getIsfMgdl(bgTime) + val autosensData = AutosensData(injector) + autosensData.time = bgTime + if (previous != null) autosensData.activeCarbsList = previous.cloneCarbsList() else autosensData.activeCarbsList = ArrayList() + + //console.error(bgTime , bucketed_data[i].glucose); + var avgDelta: Double + var delta: Double + val bg: Double = bucketedData[i].value + if (bg < 39 || bucketedData[i + 3].value < 39) { + aapsLogger.error("! value < 39") + continue + } + autosensData.bg = bg + delta = bg - bucketedData[i + 1].value + avgDelta = (bg - bucketedData[i + 3].value) / 3 + val iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile) + val bgi = -iob.activity * sens * 5 + val deviation = delta - bgi + val avgDeviation = ((avgDelta - bgi) * 1000).roundToLong() / 1000.0 + var slopeFromMaxDeviation = 0.0 + var slopeFromMinDeviation = 999.0 + + // https://github.com/openaps/oref0/blob/master/lib/determine-basal/cob-autosens.js#L169 + if (i < bucketedData.size - 16) { // we need 1h of data to calculate minDeviationSlope + @Suppress("UNUSED_VARIABLE") var maxDeviation = 0.0 + @Suppress("UNUSED_VARIABLE") var minDeviation = 999.0 + val hourAgo = bgTime + 10 * 1000 - 60 * 60 * 1000L + val hourAgoData = iobCobCalculatorPlugin.getAutosensData(hourAgo) + if (hourAgoData != null) { + val initialIndex = autosensDataTable.indexOfKey(hourAgoData.time) + aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketedData.size + " i=" + i + " hourAgoData=" + hourAgoData.toString()) + var past = 1 + try { + while (past < 12) { + val ad = autosensDataTable.valueAt(initialIndex + past) + aapsLogger.debug(LTag.AUTOSENS, ">>>>> past=" + past + " ad=" + ad?.toString()) + if (ad == null) { + aapsLogger.debug(LTag.AUTOSENS, autosensDataTable.toString()) + aapsLogger.debug(LTag.AUTOSENS, bucketedData.toString()) + aapsLogger.debug(LTag.AUTOSENS, iobCobCalculatorPlugin.bgReadings.toString()) + val notification = Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW) + rxBus.send(EventNewNotification(notification)) + sp.putBoolean("log_AUTOSENS", true) + break + } + // let it here crash on NPE to get more data as i cannot reproduce this bug + val deviationSlope = (ad.avgDeviation - avgDeviation) / (ad.time - bgTime) * 1000 * 60 * 5 + if (ad.avgDeviation > maxDeviation) { + slopeFromMaxDeviation = min(0.0, deviationSlope) + maxDeviation = ad.avgDeviation + } + if (ad.avgDeviation < minDeviation) { + slopeFromMinDeviation = max(0.0, deviationSlope) + minDeviation = ad.avgDeviation + } + past++ + } + } catch (e: Exception) { + aapsLogger.error("Unhandled exception", e) + fabricPrivacy.logException(e) + aapsLogger.debug(autosensDataTable.toString()) + aapsLogger.debug(bucketedData.toString()) + aapsLogger.debug(iobCobCalculatorPlugin.bgReadings.toString()) + val notification = Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW) + rxBus.send(EventNewNotification(notification)) + sp.putBoolean("log_AUTOSENS", true) + break + } + } else { + aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketedData.size + " i=" + i + " hourAgoData=" + "null") + } + } + val recentCarbTreatments = treatmentsPlugin.getCarbTreatments5MinBackFromHistory(bgTime) + for (recentCarbTreatment in recentCarbTreatments) { + autosensData.carbsFromBolus += recentCarbTreatment.carbs + val isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled() + autosensData.activeCarbsList.add(autosensData.CarbsInPast(recentCarbTreatment, isAAPSOrWeighted)) + autosensData.pastSensitivity += "[" + DecimalFormatter.to0Decimal(recentCarbTreatment.carbs) + "g]" + } + + // if we are absorbing carbs + if (previous != null && previous.cob > 0) { + // calculate sum of min carb impact from all active treatments + val totalMinCarbsImpact = sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact) + + // figure out how many carbs that represents + // but always assume at least 3mg/dL/5m (default) absorption per active treatment + val ci = max(deviation, totalMinCarbsImpact) + if (ci != deviation) autosensData.failoverToMinAbsorbtionRate = true + autosensData.absorbed = ci * profile.getIc(bgTime) / sens + // and add that to the running total carbsAbsorbed + autosensData.cob = max(previous.cob - autosensData.absorbed, 0.0) + autosensData.mealCarbs = previous.mealCarbs + autosensData.substractAbosorbedCarbs() + autosensData.usedMinCarbsImpact = totalMinCarbsImpact + autosensData.absorbing = previous.absorbing + autosensData.mealStartCounter = previous.mealStartCounter + autosensData.type = previous.type + autosensData.uam = previous.uam + } + val isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled() + autosensData.removeOldCarbs(bgTime, isAAPSOrWeighted) + autosensData.cob += autosensData.carbsFromBolus + autosensData.mealCarbs += autosensData.carbsFromBolus + autosensData.deviation = deviation + autosensData.bgi = bgi + autosensData.delta = delta + autosensData.avgDelta = avgDelta + autosensData.avgDeviation = avgDeviation + autosensData.slopeFromMaxDeviation = slopeFromMaxDeviation + autosensData.slopeFromMinDeviation = slopeFromMinDeviation + + // If mealCOB is zero but all deviations since hitting COB=0 are positive, exclude from autosens + if (autosensData.cob > 0 || autosensData.absorbing || autosensData.mealCarbs > 0) { + autosensData.absorbing = deviation > 0 + // stop excluding positive deviations as soon as mealCOB=0 if meal has been absorbing for >5h + if (autosensData.mealStartCounter > 60 && autosensData.cob < 0.5) { + autosensData.absorbing = false + } + if (!autosensData.absorbing && autosensData.cob < 0.5) { + autosensData.mealCarbs = 0.0 + } + // check previous "type" value, and if it wasn't csf, set a mealAbsorption start flag + if (autosensData.type != "csf") { +// process.stderr.write("("); + autosensData.mealStartCounter = 0 + } + autosensData.mealStartCounter++ + autosensData.type = "csf" + } else { + // check previous "type" value, and if it was csf, set a mealAbsorption end flag + val currentBasal = profile.getBasal(bgTime) + // always exclude the first 45m after each carb entry + //if (iob.iob > currentBasal || uam ) { + if (iob.iob > 2 * currentBasal || autosensData.uam || autosensData.mealStartCounter < 9) { + autosensData.mealStartCounter++ + autosensData.uam = deviation > 0 + autosensData.type = "uam" + } else { + autosensData.type = "non-meal" + } + } + + // Exclude meal-related deviations (carb absorption) from autosens + when (autosensData.type) { + "non-meal" -> { + when { + abs(deviation) < Constants.DEVIATION_TO_BE_EQUAL -> { + autosensData.pastSensitivity += "=" + autosensData.validDeviation = true + } + + deviation > 0 -> { + autosensData.pastSensitivity += "+" + autosensData.validDeviation = true + } + + else -> { + autosensData.pastSensitivity += "-" + autosensData.validDeviation = true + } + } + } + + "uam" -> { + autosensData.pastSensitivity += "u" + } + + else -> { + autosensData.pastSensitivity += "x" + } + } + + // add an extra negative deviation if a high temptarget is running and exercise mode is set + // TODO AS-FIX + @Suppress("SimplifyBooleanWithConstants") + if (false && sp.getBoolean(R.string.key_high_temptarget_raises_sensitivity, SMBDefaults.high_temptarget_raises_sensitivity)) { + val tempTarget = treatmentsPlugin.getTempTargetFromHistory(bgTime) + if (tempTarget != null && tempTarget.target() >= 100) { + autosensData.extraDeviation.add(-(tempTarget.target() - 100) / 20) + } + } + + // add one neutral deviation every 2 hours to help decay over long exclusion periods + val calendar = GregorianCalendar() + calendar.timeInMillis = bgTime + val min = calendar[Calendar.MINUTE] + val hours = calendar[Calendar.HOUR_OF_DAY] + if (min in 0..4 && hours % 2 == 0) autosensData.extraDeviation.add(0.0) + previous = autosensData + if (bgTime < DateUtil.now()) autosensDataTable.put(bgTime, autosensData) + aapsLogger.debug(LTag.AUTOSENS, "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + iobCobCalculatorPlugin.lastDataTime()) + val sensitivity = iobCobCalculatorPlugin.detectSensitivityWithLock(oldestTimeWithData, bgTime) + aapsLogger.debug(LTag.AUTOSENS, "Sensitivity result: $sensitivity") + autosensData.autosensResult = sensitivity + aapsLogger.debug(LTag.AUTOSENS, autosensData.toString()) + } + } + Thread { + SystemClock.sleep(1000) + rxBus.send(EventAutosensCalculationFinished(cause)) + }.start() + } finally { + mWakeLock?.release() + rxBus.send(EventIobCalculationProgress("")) + aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from") + aapsLogger.debug(LTag.AUTOSENS, "Midnights: " + MidnightTime.log()) + profiler.log(LTag.AUTOSENS, "IobCobOref1Thread", start) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java deleted file mode 100644 index 6373a1103e..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java +++ /dev/null @@ -1,326 +0,0 @@ -package info.nightscout.androidaps.plugins.iob.iobCobCalculator; - -import android.content.Context; -import android.os.PowerManager; -import android.os.SystemClock; - -import androidx.collection.LongSparseArray; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -import dagger.android.HasAndroidInjector; -import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.IobTotal; -import info.nightscout.androidaps.data.Profile; -import info.nightscout.androidaps.db.Treatment; -import info.nightscout.androidaps.events.Event; -import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.interfaces.ProfileFunction; -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults; -import info.nightscout.androidaps.plugins.bus.RxBusWrapper; -import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; -import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensBgLoaded; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.DecimalFormatter; -import info.nightscout.androidaps.utils.FabricPrivacy; -import info.nightscout.androidaps.utils.MidnightTime; -import info.nightscout.androidaps.utils.Profiler; -import info.nightscout.androidaps.utils.T; -import info.nightscout.androidaps.utils.buildHelper.BuildHelper; -import info.nightscout.androidaps.utils.resources.ResourceHelper; -import info.nightscout.androidaps.utils.sharedPreferences.SP; - -import static info.nightscout.androidaps.utils.DateUtil.now; - -/** - * Created by mike on 23.01.2018. - */ - -public class IobCobThread extends Thread { - private final Event cause; - - @Inject AAPSLogger aapsLogger; - @Inject SP sp; - @Inject RxBusWrapper rxBus; - @Inject ResourceHelper resourceHelper; - @Inject ProfileFunction profileFunction; - @Inject Context context; - @Inject SensitivityAAPSPlugin sensitivityAAPSPlugin; - @Inject SensitivityWeightedAveragePlugin sensitivityWeightedAveragePlugin; - @Inject BuildHelper buildHelper; - @Inject Profiler profiler; - @Inject FabricPrivacy fabricPrivacy; - @Inject DateUtil dateUtil; - - private final HasAndroidInjector injector; - private final IobCobCalculatorPlugin iobCobCalculatorPlugin; // cannot be injected : HistoryBrowser uses different instance - private final TreatmentsPlugin treatmentsPlugin; // cannot be injected : HistoryBrowser uses different instance - private final boolean bgDataReload; - private final boolean limitDataToOldestAvailable; - private final String from; - private final long end; - - private PowerManager.WakeLock mWakeLock; - - @Inject IobCobThread(HasAndroidInjector injector, IobCobCalculatorPlugin iobCobCalculatorPlugin, TreatmentsPlugin treatmentsPlugin, String from, long end, boolean bgDataReload, boolean limitDataToOldestAvailable, Event cause) { - super(); - injector.androidInjector().inject(this); - this.injector = injector; - this.iobCobCalculatorPlugin = iobCobCalculatorPlugin; - this.treatmentsPlugin = treatmentsPlugin; - - this.bgDataReload = bgDataReload; - this.limitDataToOldestAvailable = limitDataToOldestAvailable; - this.from = from; - this.cause = cause; - this.end = end; - - PowerManager powerManager = (PowerManager) context.getApplicationContext().getSystemService(Context.POWER_SERVICE); - if (powerManager != null) - mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, resourceHelper.gs(R.string.app_name) + ":iobCobThread"); - } - - @Override - public final void run() { - long start = DateUtil.now(); - if (mWakeLock != null) - mWakeLock.acquire(T.mins(10).msecs()); - try { - aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread started: " + from); - if (!profileFunction.isProfileValid("IobCobThread")) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No profile): " + from); - return; // app still initializing - } - //log.debug("Locking calculateSensitivityData"); - - long oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable); - - synchronized (iobCobCalculatorPlugin.getDataLock()) { - if (bgDataReload) { - iobCobCalculatorPlugin.loadBgData(end); - iobCobCalculatorPlugin.createBucketedData(); - rxBus.send(new EventAutosensBgLoaded(cause)); - } - List bucketed_data = iobCobCalculatorPlugin.getBucketedData(); - LongSparseArray autosensDataTable = iobCobCalculatorPlugin.getAutosensDataTable(); - - if (bucketed_data == null || bucketed_data.size() < 3) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): " + from); - return; - } - - long prevDataTime = IobCobCalculatorPlugin.Companion.roundUpTime(bucketed_data.get(bucketed_data.size() - 3).getTimestamp()); - aapsLogger.debug(LTag.AUTOSENS, "Prev data time: " + dateUtil.dateAndTimeString(prevDataTime)); - AutosensData previous = autosensDataTable.get(prevDataTime); - // start from oldest to be able sub cob - for (int i = bucketed_data.size() - 4; i >= 0; i--) { - String progress = i + (buildHelper.isDev() ? " (" + from + ")" : ""); - rxBus.send(new EventIobCalculationProgress(progress)); - - if (iobCobCalculatorPlugin.getStopCalculationTrigger()) { - iobCobCalculatorPlugin.setStopCalculationTrigger(false); - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): " + from); - return; - } - // check if data already exists - long bgTime = bucketed_data.get(i).getTimestamp(); - bgTime = IobCobCalculatorPlugin.Companion.roundUpTime(bgTime); - if (bgTime > IobCobCalculatorPlugin.Companion.roundUpTime(now())) - continue; - - AutosensData existing; - if ((existing = autosensDataTable.get(bgTime)) != null) { - previous = existing; - continue; - } - - Profile profile = profileFunction.getProfile(bgTime); - if (profile == null) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): " + from); - return; // profile not set yet - } - - aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: " + from + " (" + i + "/" + bucketed_data.size() + ")"); - - double sens = profile.getIsfMgdl(bgTime); - - AutosensData autosensData = new AutosensData(injector); - autosensData.time = bgTime; - if (previous != null) - autosensData.activeCarbsList = previous.cloneCarbsList(); - else - autosensData.activeCarbsList = new ArrayList<>(); - - //console.error(bgTime , bucketed_data[i].glucose); - double bg; - double avgDelta; - double delta; - bg = bucketed_data.get(i).getValue(); - if (bg < 39 || bucketed_data.get(i + 3).getValue() < 39) { - aapsLogger.error("! value < 39"); - continue; - } - autosensData.bg = bg; - delta = (bg - bucketed_data.get(i + 1).getValue()); - avgDelta = (bg - bucketed_data.get(i + 3).getValue()) / 3; - - IobTotal iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile); - - double bgi = -iob.activity * sens * 5; - double deviation = delta - bgi; - double avgDeviation = Math.round((avgDelta - bgi) * 1000) / 1000d; - - double slopeFromMaxDeviation = 0; - double slopeFromMinDeviation = 999; - double maxDeviation = 0; - double minDeviation = 999; - - // https://github.com/openaps/oref0/blob/master/lib/determine-basal/cob-autosens.js#L169 - if (i < bucketed_data.size() - 16) { // we need 1h of data to calculate minDeviationSlope - long hourAgo = bgTime + 10 * 1000 - 60 * 60 * 1000L; - AutosensData hourAgoData = iobCobCalculatorPlugin.getAutosensData(hourAgo); - if (hourAgoData != null) { - int initialIndex = autosensDataTable.indexOfKey(hourAgoData.time); - aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketed_data.size() + " i=" + i + " hourAgoData=" + hourAgoData.toString()); - int past = 1; - try { - for (; past < 12; past++) { - AutosensData ad = autosensDataTable.valueAt(initialIndex + past); - aapsLogger.debug(LTag.AUTOSENS, ">>>>> past=" + past + " ad=" + (ad != null ? ad.toString() : null)); - if (ad == null) { - aapsLogger.debug(LTag.AUTOSENS, autosensDataTable.toString()); - aapsLogger.debug(LTag.AUTOSENS, bucketed_data.toString()); - aapsLogger.debug(LTag.AUTOSENS, iobCobCalculatorPlugin.getBgReadings().toString()); - Notification notification = new Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW); - rxBus.send(new EventNewNotification(notification)); - sp.putBoolean("log_AUTOSENS", true); - break; - } - // let it here crash on NPE to get more data as i cannot reproduce this bug - double deviationSlope = (ad.avgDeviation - avgDeviation) / (ad.time - bgTime) * 1000 * 60 * 5; - if (ad.avgDeviation > maxDeviation) { - slopeFromMaxDeviation = Math.min(0, deviationSlope); - maxDeviation = ad.avgDeviation; - } - if (ad.avgDeviation < minDeviation) { - slopeFromMinDeviation = Math.max(0, deviationSlope); - minDeviation = ad.avgDeviation; - } - } - } catch (Exception e) { - aapsLogger.error("Unhandled exception", e); - fabricPrivacy.logException(e); - aapsLogger.debug(autosensDataTable.toString()); - aapsLogger.debug(bucketed_data.toString()); - aapsLogger.debug(iobCobCalculatorPlugin.getBgReadings().toString()); - Notification notification = new Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW); - rxBus.send(new EventNewNotification(notification)); - sp.putBoolean("log_AUTOSENS", true); - break; - } - } else { - aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketed_data.size() + " i=" + i + " hourAgoData=" + "null"); - } - } - - List recentCarbTreatments = treatmentsPlugin.getCarbTreatments5MinBackFromHistory(bgTime); - for (Treatment recentCarbTreatment : recentCarbTreatments) { - autosensData.carbsFromBolus += recentCarbTreatment.carbs; - boolean isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled(); - autosensData.activeCarbsList.add(autosensData.new CarbsInPast(recentCarbTreatment, isAAPSOrWeighted)); - autosensData.pastSensitivity += "[" + DecimalFormatter.to0Decimal(recentCarbTreatment.carbs) + "g]"; - } - - - // if we are absorbing carbs - if (previous != null && previous.cob > 0) { - // calculate sum of min carb impact from all active treatments - double totalMinCarbsImpact = 0d; - if (sensitivityAAPSPlugin.isEnabled(PluginType.SENSITIVITY) || sensitivityWeightedAveragePlugin.isEnabled(PluginType.SENSITIVITY)) { - //when the impact depends on a max time, sum them up as smaller carb sizes make them smaller - for (int ii = 0; ii < autosensData.activeCarbsList.size(); ++ii) { - AutosensData.CarbsInPast c = autosensData.activeCarbsList.get(ii); - totalMinCarbsImpact += c.min5minCarbImpact; - } - } else { - //Oref sensitivity - totalMinCarbsImpact = sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact); - } - - // figure out how many carbs that represents - // but always assume at least 3mg/dL/5m (default) absorption per active treatment - double ci = Math.max(deviation, totalMinCarbsImpact); - if (ci != deviation) - autosensData.failoverToMinAbsorbtionRate = true; - autosensData.absorbed = ci * profile.getIc(bgTime) / sens; - // and add that to the running total carbsAbsorbed - autosensData.cob = Math.max(previous.cob - autosensData.absorbed, 0d); - autosensData.substractAbosorbedCarbs(); - autosensData.usedMinCarbsImpact = totalMinCarbsImpact; - } - boolean isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled(); - autosensData.removeOldCarbs(bgTime, isAAPSOrWeighted); - autosensData.cob += autosensData.carbsFromBolus; - autosensData.deviation = deviation; - autosensData.bgi = bgi; - autosensData.delta = delta; - autosensData.avgDelta = avgDelta; - autosensData.avgDeviation = avgDeviation; - autosensData.slopeFromMaxDeviation = slopeFromMaxDeviation; - autosensData.slopeFromMinDeviation = slopeFromMinDeviation; - - - // calculate autosens only without COB - if (autosensData.cob <= 0) { - if (Math.abs(deviation) < Constants.DEVIATION_TO_BE_EQUAL) { - autosensData.pastSensitivity += "="; - autosensData.validDeviation = true; - } else if (deviation > 0) { - autosensData.pastSensitivity += "+"; - autosensData.validDeviation = true; - } else { - autosensData.pastSensitivity += "-"; - autosensData.validDeviation = true; - } - } else { - autosensData.pastSensitivity += "C"; - } - - previous = autosensData; - if (bgTime < now()) - autosensDataTable.put(bgTime, autosensData); - aapsLogger.debug(LTag.AUTOSENS, "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + iobCobCalculatorPlugin.lastDataTime()); - AutosensResult sensitivity = iobCobCalculatorPlugin.detectSensitivityWithLock(oldestTimeWithData, bgTime); - aapsLogger.debug(LTag.AUTOSENS, "Sensitivity result: " + sensitivity.toString()); - autosensData.autosensResult = sensitivity; - aapsLogger.debug(LTag.AUTOSENS, autosensData.toString()); - } - } - new Thread(() -> { - SystemClock.sleep(1000); - rxBus.send(new EventAutosensCalculationFinished(cause)); - }).start(); - } finally { - if (mWakeLock != null) - mWakeLock.release(); - rxBus.send(new EventIobCalculationProgress("")); - aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: " + from); - aapsLogger.debug(LTag.AUTOSENS, "Midnights: " + MidnightTime.log()); - profiler.log(LTag.AUTOSENS, "IobCobThread", start); - } - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt new file mode 100644 index 0000000000..1f67032978 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt @@ -0,0 +1,277 @@ +package info.nightscout.androidaps.plugins.iob.iobCobCalculator + +import android.content.Context +import android.os.PowerManager +import android.os.SystemClock +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.R +import info.nightscout.androidaps.events.Event +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin.Companion.roundUpTime +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensBgLoaded +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress +import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin +import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.utils.* +import info.nightscout.androidaps.utils.buildHelper.BuildHelper +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP +import java.util.* +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min +import kotlin.math.roundToLong + +class IobCobThread @Inject internal constructor( + private val injector: HasAndroidInjector, + private val iobCobCalculatorPlugin: IobCobCalculatorPlugin, // cannot be injected : HistoryBrowser uses different instance + private val treatmentsPlugin: TreatmentsPlugin, // cannot be injected : HistoryBrowser uses different instance + private val from: String, + private val end: Long, + private val bgDataReload: Boolean, + private val limitDataToOldestAvailable: Boolean, + private val cause: Event +) : Thread() { + + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var sp: SP + @Inject lateinit var rxBus: RxBusWrapper + @Inject lateinit var resourceHelper: ResourceHelper + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var context: Context + @Inject lateinit var sensitivityAAPSPlugin: SensitivityAAPSPlugin + @Inject lateinit var sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin + @Inject lateinit var buildHelper: BuildHelper + @Inject lateinit var profiler: Profiler + @Inject lateinit var fabricPrivacy: FabricPrivacy + @Inject lateinit var dateUtil: DateUtil + + private var mWakeLock: PowerManager.WakeLock? = null + + init { + injector.androidInjector().inject(this) + mWakeLock = (context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, resourceHelper.gs(R.string.app_name) + ":iobCobThread") + } + + override fun run() { + val start = DateUtil.now() + mWakeLock?.acquire(T.mins(10).msecs()) + try { + aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread started: $from") + if (!profileFunction.isProfileValid("IobCobThread")) { + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No profile): $from") + return // app still initializing + } + //log.debug("Locking calculateSensitivityData"); + val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable) + synchronized(iobCobCalculatorPlugin.dataLock) { + if (bgDataReload) { + iobCobCalculatorPlugin.loadBgData(end) + iobCobCalculatorPlugin.createBucketedData() + rxBus.send(EventAutosensBgLoaded(cause)) + } + val bucketedData = iobCobCalculatorPlugin.bucketedData + val autosensDataTable = iobCobCalculatorPlugin.autosensDataTable + if (bucketedData == null || bucketedData.size < 3) { + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): $from") + return + } + val prevDataTime = roundUpTime(bucketedData[bucketedData.size - 3].timestamp) + aapsLogger.debug(LTag.AUTOSENS, "Prev data time: " + dateUtil.dateAndTimeString(prevDataTime)) + var previous = autosensDataTable[prevDataTime] + // start from oldest to be able sub cob + for (i in bucketedData.size - 4 downTo 0) { + val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else "" + rxBus.send(EventIobCalculationProgress(progress)) + if (iobCobCalculatorPlugin.stopCalculationTrigger) { + iobCobCalculatorPlugin.stopCalculationTrigger = false + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from") + return + } + // check if data already exists + var bgTime = bucketedData[i].timestamp + bgTime = roundUpTime(bgTime) + if (bgTime > roundUpTime(DateUtil.now())) continue + var existing: AutosensData? + if (autosensDataTable[bgTime].also { existing = it } != null) { + previous = existing + continue + } + val profile = profileFunction.getProfile(bgTime) + if (profile == null) { + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): $from") + return // profile not set yet + } + aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: " + from + " (" + i + "/" + bucketedData.size + ")") + val sens = profile.getIsfMgdl(bgTime) + val autosensData = AutosensData(injector) + autosensData.time = bgTime + if (previous != null) autosensData.activeCarbsList = previous.cloneCarbsList() else autosensData.activeCarbsList = ArrayList() + + //console.error(bgTime , bucketed_data[i].glucose); + var avgDelta: Double + var delta: Double + val bg: Double = bucketedData[i].value + if (bg < 39 || bucketedData[i + 3].value < 39) { + aapsLogger.error("! value < 39") + continue + } + autosensData.bg = bg + delta = bg - bucketedData[i + 1].value + avgDelta = (bg - bucketedData[i + 3].value) / 3 + val iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile) + val bgi = -iob.activity * sens * 5 + val deviation = delta - bgi + val avgDeviation = ((avgDelta - bgi) * 1000).roundToLong() / 1000.0 + var slopeFromMaxDeviation = 0.0 + var slopeFromMinDeviation = 999.0 + + // https://github.com/openaps/oref0/blob/master/lib/determine-basal/cob-autosens.js#L169 + if (i < bucketedData.size - 16) { // we need 1h of data to calculate minDeviationSlope + @Suppress("UNUSED_VARIABLE") var maxDeviation = 0.0 + @Suppress("UNUSED_VARIABLE") var minDeviation = 999.0 + val hourAgo = bgTime + 10 * 1000 - 60 * 60 * 1000L + val hourAgoData = iobCobCalculatorPlugin.getAutosensData(hourAgo) + if (hourAgoData != null) { + val initialIndex = autosensDataTable.indexOfKey(hourAgoData.time) + aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketedData.size + " i=" + i + " hourAgoData=" + hourAgoData.toString()) + var past = 1 + try { + while (past < 12) { + val ad = autosensDataTable.valueAt(initialIndex + past) + aapsLogger.debug(LTag.AUTOSENS, ">>>>> past=" + past + " ad=" + ad?.toString()) + if (ad == null) { + aapsLogger.debug(LTag.AUTOSENS, autosensDataTable.toString()) + aapsLogger.debug(LTag.AUTOSENS, bucketedData.toString()) + aapsLogger.debug(LTag.AUTOSENS, iobCobCalculatorPlugin.bgReadings.toString()) + val notification = Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW) + rxBus.send(EventNewNotification(notification)) + sp.putBoolean("log_AUTOSENS", true) + break + } + // let it here crash on NPE to get more data as i cannot reproduce this bug + val deviationSlope = (ad.avgDeviation - avgDeviation) / (ad.time - bgTime) * 1000 * 60 * 5 + if (ad.avgDeviation > maxDeviation) { + slopeFromMaxDeviation = min(0.0, deviationSlope) + maxDeviation = ad.avgDeviation + } + if (ad.avgDeviation < minDeviation) { + slopeFromMinDeviation = max(0.0, deviationSlope) + minDeviation = ad.avgDeviation + } + past++ + } + } catch (e: Exception) { + aapsLogger.error("Unhandled exception", e) + fabricPrivacy.logException(e) + aapsLogger.debug(autosensDataTable.toString()) + aapsLogger.debug(bucketedData.toString()) + aapsLogger.debug(iobCobCalculatorPlugin.bgReadings.toString()) + val notification = Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW) + rxBus.send(EventNewNotification(notification)) + sp.putBoolean("log_AUTOSENS", true) + break + } + } else { + aapsLogger.debug(LTag.AUTOSENS, ">>>>> bucketed_data.size()=" + bucketedData.size + " i=" + i + " hourAgoData=" + "null") + } + } + val recentCarbTreatments = treatmentsPlugin.getCarbTreatments5MinBackFromHistory(bgTime) + for (recentCarbTreatment in recentCarbTreatments) { + autosensData.carbsFromBolus += recentCarbTreatment.carbs + val isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled() + autosensData.activeCarbsList.add(autosensData.CarbsInPast(recentCarbTreatment, isAAPSOrWeighted)) + autosensData.pastSensitivity += "[" + DecimalFormatter.to0Decimal(recentCarbTreatment.carbs) + "g]" + } + + // if we are absorbing carbs + if (previous != null && previous.cob > 0) { + // calculate sum of min carb impact from all active treatments + var totalMinCarbsImpact = 0.0 + if (sensitivityAAPSPlugin.isEnabled(PluginType.SENSITIVITY) || sensitivityWeightedAveragePlugin.isEnabled(PluginType.SENSITIVITY)) { + //when the impact depends on a max time, sum them up as smaller carb sizes make them smaller + for (ii in autosensData.activeCarbsList.indices) { + val c = autosensData.activeCarbsList[ii] + totalMinCarbsImpact += c.min5minCarbImpact + } + } else { + //Oref sensitivity + totalMinCarbsImpact = sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact) + } + + // figure out how many carbs that represents + // but always assume at least 3mg/dL/5m (default) absorption per active treatment + val ci = max(deviation, totalMinCarbsImpact) + if (ci != deviation) autosensData.failoverToMinAbsorbtionRate = true + autosensData.absorbed = ci * profile.getIc(bgTime) / sens + // and add that to the running total carbsAbsorbed + autosensData.cob = max(previous.cob - autosensData.absorbed, 0.0) + autosensData.substractAbosorbedCarbs() + autosensData.usedMinCarbsImpact = totalMinCarbsImpact + } + val isAAPSOrWeighted = sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled() + autosensData.removeOldCarbs(bgTime, isAAPSOrWeighted) + autosensData.cob += autosensData.carbsFromBolus + autosensData.deviation = deviation + autosensData.bgi = bgi + autosensData.delta = delta + autosensData.avgDelta = avgDelta + autosensData.avgDeviation = avgDeviation + autosensData.slopeFromMaxDeviation = slopeFromMaxDeviation + autosensData.slopeFromMinDeviation = slopeFromMinDeviation + + // calculate autosens only without COB + if (autosensData.cob <= 0) { + when { + abs(deviation) < Constants.DEVIATION_TO_BE_EQUAL -> { + autosensData.pastSensitivity += "=" + autosensData.validDeviation = true + } + + deviation > 0 -> { + autosensData.pastSensitivity += "+" + autosensData.validDeviation = true + } + + else -> { + autosensData.pastSensitivity += "-" + autosensData.validDeviation = true + } + } + } else { + autosensData.pastSensitivity += "C" + } + previous = autosensData + if (bgTime < DateUtil.now()) autosensDataTable.put(bgTime, autosensData) + aapsLogger.debug(LTag.AUTOSENS, "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + iobCobCalculatorPlugin.lastDataTime()) + val sensitivity = iobCobCalculatorPlugin.detectSensitivityWithLock(oldestTimeWithData, bgTime) + aapsLogger.debug(LTag.AUTOSENS, "Sensitivity result: $sensitivity") + autosensData.autosensResult = sensitivity + aapsLogger.debug(LTag.AUTOSENS, autosensData.toString()) + } + } + Thread { + SystemClock.sleep(1000) + rxBus.send(EventAutosensCalculationFinished(cause)) + }.start() + } finally { + mWakeLock?.release() + rxBus.send(EventIobCalculationProgress("")) + aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from") + aapsLogger.debug(LTag.AUTOSENS, "Midnights: " + MidnightTime.log()) + profiler.log(LTag.AUTOSENS, "IobCobThread", start) + } + } +} \ No newline at end of file