IobThreads -> kt

This commit is contained in:
Milos Kozak 2021-02-16 18:47:35 +01:00
parent a2c0fa7643
commit 32df0b9490
5 changed files with 613 additions and 738 deletions

View file

@ -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)

View file

@ -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<InMemoryGlucoseValue> bucketed_data = iobCobCalculatorPlugin.getBucketedData();
LongSparseArray<AutosensData> 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<Treatment> 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);
}
}
}

View file

@ -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)
}
}
}

View file

@ -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<InMemoryGlucoseValue> bucketed_data = iobCobCalculatorPlugin.getBucketedData();
LongSparseArray<AutosensData> 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<Treatment> 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);
}
}
}

View file

@ -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)
}
}
}