HistoryBrowseActivity

This commit is contained in:
Milos Kozak 2021-05-25 17:42:21 +02:00
parent 966f7b35f2
commit bee6d289c0
21 changed files with 1104 additions and 1143 deletions

View file

@ -68,7 +68,7 @@
</intent-filter>
</activity>
<activity android:name=".plugins.general.maintenance.activities.PrefImportListActivity" />
<activity android:name=".historyBrowser.HistoryBrowseActivity" />
<activity android:name=".activities.HistoryBrowseActivity" />
<activity android:name=".activities.TreatmentsActivity" />
<activity android:name=".activities.SurveyActivity" />
<activity android:name=".activities.ProfileHelperActivity"

View file

@ -34,7 +34,7 @@ import info.nightscout.androidaps.databinding.ActivityMainBinding
import info.nightscout.androidaps.events.EventAppExit
import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.events.EventRebuildTabs
import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity
import info.nightscout.androidaps.activities.HistoryBrowseActivity
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.IconsProvider

View file

@ -0,0 +1,391 @@
package info.nightscout.androidaps.activities
import android.annotation.SuppressLint
import android.app.DatePickerDialog
import android.graphics.Color
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.TextView
import com.jjoe64.graphview.GraphView
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.databinding.ActivityHistorybrowseBinding
import info.nightscout.androidaps.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.events.EventCustomCalculationFinished
import info.nightscout.androidaps.events.EventRefreshOverview
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
import info.nightscout.androidaps.plugins.general.overview.OverviewData
import info.nightscout.androidaps.plugins.general.overview.OverviewMenus
import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventBucketedDataCreated
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress
import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import java.util.*
import javax.inject.Inject
import kotlin.math.min
class HistoryBrowseActivity : NoSplashAppCompatActivity() {
@Inject lateinit var injector: HasAndroidInjector
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var sp: SP
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var defaultValueHelper: DefaultValueHelper
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var buildHelper: BuildHelper
@Inject lateinit var sensitivityOref1Plugin: SensitivityOref1Plugin
@Inject lateinit var sensitivityAAPSPlugin: SensitivityAAPSPlugin
@Inject lateinit var sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin
@Inject lateinit var repository: AppRepository
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var overviewMenus: OverviewMenus
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var config: Config
@Inject lateinit var loopPlugin: LoopPlugin
@Inject lateinit var nsDeviceStatus: NSDeviceStatus
@Inject lateinit var translator: Translator
private val disposable = CompositeDisposable()
private val secondaryGraphs = ArrayList<GraphView>()
private val secondaryGraphsLabel = ArrayList<TextView>()
private var axisWidth: Int = 0
private var rangeToDisplay = 24 // for graph
// private var start: Long = 0
private lateinit var iobCobCalculator: IobCobCalculatorPlugin
private lateinit var overviewData: OverviewData
private lateinit var binding: ActivityHistorybrowseBinding
private var destroyed = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHistorybrowseBinding.inflate(layoutInflater)
setContentView(binding.root)
// We don't want to use injected singletons but own instance working on top of different data
iobCobCalculator = IobCobCalculatorPlugin(injector, aapsLogger, aapsSchedulers, rxBus, sp, resourceHelper, profileFunction, activePlugin, sensitivityOref1Plugin, sensitivityAAPSPlugin, sensitivityWeightedAveragePlugin, fabricPrivacy, dateUtil, repository)
overviewData = OverviewData(injector, aapsLogger, resourceHelper, dateUtil, sp, activePlugin, defaultValueHelper, profileFunction, config, loopPlugin, nsDeviceStatus, repository, overviewMenus, iobCobCalculator, translator)
binding.left.setOnClickListener {
setTime(overviewData.fromTime - T.hours(rangeToDisplay.toLong()).msecs())
loadAll("onClickLeft")
}
binding.right.setOnClickListener {
setTime(overviewData.fromTime + T.hours(rangeToDisplay.toLong()).msecs())
loadAll("onClickRight")
}
binding.end.setOnClickListener {
setTime(dateUtil.now())
loadAll("onClickEnd")
}
binding.zoom.setOnClickListener {
rangeToDisplay += 6
rangeToDisplay = if (rangeToDisplay > 24) 6 else rangeToDisplay
setTime(overviewData.fromTime)
loadAll("rangeChange")
}
binding.zoom.setOnLongClickListener {
Calendar.getInstance().also { calendar ->
calendar.timeInMillis = overviewData.fromTime
calendar[Calendar.MILLISECOND] = 0
calendar[Calendar.SECOND] = 0
calendar[Calendar.MINUTE] = 0
calendar[Calendar.HOUR_OF_DAY] = 0
setTime(calendar.timeInMillis)
}
loadAll("onLongClickZoom")
true
}
// create an OnDateSetListener
val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth ->
Calendar.getInstance().also { calendar ->
calendar.timeInMillis = overviewData.fromTime
calendar[Calendar.YEAR] = year
calendar[Calendar.MONTH] = monthOfYear
calendar[Calendar.DAY_OF_MONTH] = dayOfMonth
calendar[Calendar.MILLISECOND] = 0
calendar[Calendar.SECOND] = 0
calendar[Calendar.MINUTE] = 0
calendar[Calendar.HOUR_OF_DAY] = 0
setTime(calendar.timeInMillis)
binding.date.text = dateUtil.dateAndTimeString(overviewData.fromTime)
}
loadAll("onClickDate")
}
binding.date.setOnClickListener {
val cal = Calendar.getInstance()
cal.timeInMillis = overviewData.fromTime
DatePickerDialog(this, dateSetListener,
cal.get(Calendar.YEAR),
cal.get(Calendar.MONTH),
cal.get(Calendar.DAY_OF_MONTH)
).show()
}
val dm = DisplayMetrics()
windowManager?.defaultDisplay?.getMetrics(dm)
axisWidth = if (dm.densityDpi <= 120) 3 else if (dm.densityDpi <= 160) 10 else if (dm.densityDpi <= 320) 35 else if (dm.densityDpi <= 420) 50 else if (dm.densityDpi <= 560) 70 else 80
binding.bgGraph.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.graphgrid)
binding.bgGraph.gridLabelRenderer?.reloadStyles()
binding.bgGraph.gridLabelRenderer?.labelVerticalWidth = axisWidth
overviewMenus.setupChartMenu(binding.chartMenuButton)
prepareGraphsIfNeeded(overviewMenus.setting.size)
savedInstanceState?.let { bundle ->
rangeToDisplay = bundle.getInt("rangeToDisplay", 0)
overviewData.fromTime = bundle.getLong("start", 0)
overviewData.toTime = bundle.getLong("end", 0)
}
}
public override fun onPause() {
super.onPause()
disposable.clear()
iobCobCalculator.stopCalculation("onPause")
}
@Synchronized
override fun onDestroy() {
destroyed = true
super.onDestroy()
}
public override fun onResume() {
super.onResume()
disposable.add(rxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
// catch only events from iobCobCalculator
if (it.cause is EventCustomCalculationFinished)
refreshLoop("EventAutosensCalculationFinished")
}, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventIobCalculationProgress::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
if (it.cause is EventCustomCalculationFinished)
binding.overviewIobcalculationprogess.text = it.progress
}, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventRefreshOverview::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ updateGUI("EventRefreshOverview") }, fabricPrivacy::logException)
)
disposable += rxBus
.toObservable(EventBucketedDataCreated::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
overviewData.prepareBucketedData("EventBucketedDataCreated")
overviewData.prepareBgData("EventBucketedDataCreated")
rxBus.send(EventRefreshOverview("EventBucketedDataCreated"))
}, fabricPrivacy::logException)
if (overviewData.fromTime == 0L) {
// set start of current day
setTime(dateUtil.now())
loadAll("onResume")
} else {
updateGUI("onResume")
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("rangeToDisplay", rangeToDisplay)
outState.putLong("start", overviewData.fromTime)
outState.putLong("end", overviewData.toTime)
}
private fun prepareGraphsIfNeeded(numOfGraphs: Int) {
if (numOfGraphs != secondaryGraphs.size - 1) {
//aapsLogger.debug("New secondary graph count ${numOfGraphs-1}")
// rebuild needed
secondaryGraphs.clear()
secondaryGraphsLabel.clear()
binding.iobGraph.removeAllViews()
for (i in 1 until numOfGraphs) {
val relativeLayout = RelativeLayout(this)
relativeLayout.layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
val graph = GraphView(this)
graph.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, resourceHelper.dpToPx(100)).also { it.setMargins(0, resourceHelper.dpToPx(15), 0, resourceHelper.dpToPx(10)) }
graph.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.graphgrid)
graph.gridLabelRenderer?.reloadStyles()
graph.gridLabelRenderer?.isHorizontalLabelsVisible = false
graph.gridLabelRenderer?.labelVerticalWidth = axisWidth
graph.gridLabelRenderer?.numVerticalLabels = 3
graph.viewport.backgroundColor = Color.argb(20, 255, 255, 255) // 8% of gray
relativeLayout.addView(graph)
val label = TextView(this)
val layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).also { it.setMargins(resourceHelper.dpToPx(30), resourceHelper.dpToPx(25), 0, 0) }
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP)
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
label.layoutParams = layoutParams
relativeLayout.addView(label)
secondaryGraphsLabel.add(label)
binding.iobGraph.addView(relativeLayout)
secondaryGraphs.add(graph)
}
}
}
@Suppress("SameParameterValue")
private fun loadAll(from: String) {
Thread {
overviewData.prepareBasalData(from)
overviewData.prepareTemporaryTargetData(from)
overviewData.prepareTreatmentsData(from)
rxBus.send(EventRefreshOverview(from))
aapsLogger.debug(LTag.UI, "loadAll $from finished")
runCalculation(from)
}.start()
}
private fun setTime(start: Long) {
Calendar.getInstance().also { calendar ->
calendar.timeInMillis = start
calendar[Calendar.MILLISECOND] = 0
calendar[Calendar.SECOND] = 0
calendar[Calendar.MINUTE] = 0
calendar[Calendar.HOUR_OF_DAY] = 0
overviewData.fromTime = calendar.timeInMillis
overviewData.toTime = overviewData.fromTime + T.hours(rangeToDisplay.toLong()).msecs()
overviewData.endTime = overviewData.toTime
}
}
private fun runCalculation(from: String) {
Thread {
iobCobCalculator.stopCalculation(from)
iobCobCalculator.stopCalculationTrigger = false
iobCobCalculator.runCalculation(from, overviewData.toTime, bgDataReload = true, limitDataToOldestAvailable = false, cause = EventCustomCalculationFinished())
}.start()
}
@Volatile
var runningRefresh = false
private fun refreshLoop(from: String) {
if (runningRefresh) return
runningRefresh = true
overviewData.prepareIobAutosensData(from)
rxBus.send(EventRefreshOverview(from))
aapsLogger.debug(LTag.UI, "refreshLoop finished")
runningRefresh = false
}
@Suppress("UNUSED_PARAMETER")
@SuppressLint("SetTextI18n")
fun updateGUI(from: String) {
aapsLogger.debug(LTag.UI, "updateGui $from")
binding.date.text = dateUtil.dateAndTimeString(overviewData.fromTime)
binding.zoom.text = rangeToDisplay.toString()
val pump = activePlugin.activePump
val graphData = GraphData(injector, binding.bgGraph, overviewData)
val menuChartSettings = overviewMenus.setting
graphData.addInRangeArea(overviewData.fromTime, overviewData.endTime, defaultValueHelper.determineLowLine(), defaultValueHelper.determineHighLine())
graphData.addBgReadings(menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal])
if (buildHelper.isDev()) graphData.addBucketedData()
graphData.addTreatments()
if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal])
graphData.addActivity(0.8)
if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal])
graphData.addBasals()
graphData.addTargetLine()
graphData.addNowLine(dateUtil.now())
// set manual x bounds to have nice steps
graphData.setNumVerticalLabels()
graphData.formatAxis(overviewData.fromTime, overviewData.endTime)
graphData.performUpdate()
// 2nd graphs
prepareGraphsIfNeeded(menuChartSettings.size)
val secondaryGraphsData: ArrayList<GraphData> = ArrayList()
val now = System.currentTimeMillis()
for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) {
val secondGraphData = GraphData(injector, secondaryGraphs[g], overviewData)
var useABSForScale = false
var useIobForScale = false
var useCobForScale = false
var useDevForScale = false
var useRatioForScale = false
var useDSForScale = false
var useBGIForScale = false
when {
menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal] -> useCobForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] -> useDevForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true
}
val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]
if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(useABSForScale, 1.0)
if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(useIobForScale, 1.0)
if (menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal]) secondGraphData.addCob(useCobForScale, if (useCobForScale) 1.0 else 0.5)
if (menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal]) secondGraphData.addDeviations(useDevForScale, 1.0)
if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8)
if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(useRatioForScale, if (useRatioForScale) 1.0 else 0.8)
if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && buildHelper.isDev()) secondGraphData.addDeviationSlope(useDSForScale, 1.0)
// set manual x bounds to have nice steps
secondGraphData.formatAxis(overviewData.fromTime, overviewData.endTime)
secondGraphData.addNowLine(now)
secondaryGraphsData.add(secondGraphData)
}
for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) {
secondaryGraphsLabel[g].text = overviewMenus.enabledTypes(g + 1)
secondaryGraphs[g].visibility = (
menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] ||
menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] ||
menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal] ||
menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] ||
menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] ||
menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] ||
menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal]
).toVisibility()
secondaryGraphsData[g].performUpdate()
}
}
}

View file

@ -4,7 +4,7 @@ import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.MainActivity
import info.nightscout.androidaps.activities.*
import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity
import info.nightscout.androidaps.activities.HistoryBrowseActivity
import info.nightscout.androidaps.plugins.general.maintenance.activities.LogSettingActivity
import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansLoginActivity
import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity

View file

@ -27,7 +27,6 @@ import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.queue.CommandQueue
import info.nightscout.androidaps.utils.*
@ -47,7 +46,6 @@ class CarbsDialog : DialogFragmentWithDate() {
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var constraintChecker: ConstraintChecker
@Inject lateinit var defaultValueHelper: DefaultValueHelper
@Inject lateinit var treatmentsPlugin: TreatmentsPlugin
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var iobCobCalculator: IobCobCalculator
@Inject lateinit var uel: UserEntryLogger

View file

@ -1,379 +0,0 @@
package info.nightscout.androidaps.historyBrowser
import android.app.DatePickerDialog
import android.graphics.Color
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.lifecycle.lifecycleScope
import com.jjoe64.graphview.GraphView
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.NoSplashAppCompatActivity
import info.nightscout.androidaps.databinding.ActivityHistorybrowseBinding
import info.nightscout.androidaps.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.events.EventCustomCalculationFinished
import info.nightscout.androidaps.events.EventRefreshOverview
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.overview.OverviewMenus
import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
import javax.inject.Inject
class HistoryBrowseActivity : NoSplashAppCompatActivity() {
@Inject lateinit var injector: HasAndroidInjector
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var sp: SP
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var defaultValueHelper: DefaultValueHelper
@Inject lateinit var iobCobCalculatorPluginHistory: IobCobCalculatorPluginHistory
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var buildHelper: BuildHelper
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var overviewMenus: OverviewMenus
@Inject lateinit var dateUtil: DateUtil
private val disposable = CompositeDisposable()
private val secondaryGraphs = ArrayList<GraphView>()
private val secondaryGraphsLabel = ArrayList<TextView>()
private var axisWidth: Int = 0
private var rangeToDisplay = 24 // for graph
private var start: Long = 0
private val graphLock = Object()
private var eventCustomCalculationFinished = EventCustomCalculationFinished()
private lateinit var binding: ActivityHistorybrowseBinding
private var destroyed = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHistorybrowseBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.left.setOnClickListener {
start -= T.hours(rangeToDisplay.toLong()).msecs()
runCalculation("onClickLeft")
}
binding.right.setOnClickListener {
start += T.hours(rangeToDisplay.toLong()).msecs()
runCalculation("onClickRight")
}
binding.end.setOnClickListener {
val calendar = Calendar.getInstance()
calendar.timeInMillis = System.currentTimeMillis()
calendar[Calendar.MILLISECOND] = 0
calendar[Calendar.SECOND] = 0
calendar[Calendar.MINUTE] = 0
calendar[Calendar.HOUR_OF_DAY] = 0
start = calendar.timeInMillis
runCalculation("onClickEnd")
}
binding.zoom.setOnClickListener {
rangeToDisplay += 6
rangeToDisplay = if (rangeToDisplay > 24) 6 else rangeToDisplay
updateGUI("rangeChange", false)
}
binding.zoom.setOnLongClickListener {
val calendar = Calendar.getInstance()
calendar.timeInMillis = start
calendar[Calendar.MILLISECOND] = 0
calendar[Calendar.SECOND] = 0
calendar[Calendar.MINUTE] = 0
calendar[Calendar.HOUR_OF_DAY] = 0
start = calendar.timeInMillis
runCalculation("onLongClickZoom")
true
}
// create an OnDateSetListener
val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth ->
val cal = Calendar.getInstance()
cal.timeInMillis = start
cal[Calendar.YEAR] = year
cal[Calendar.MONTH] = monthOfYear
cal[Calendar.DAY_OF_MONTH] = dayOfMonth
cal[Calendar.MILLISECOND] = 0
cal[Calendar.SECOND] = 0
cal[Calendar.MINUTE] = 0
cal[Calendar.HOUR_OF_DAY] = 0
start = cal.timeInMillis
binding.date.text = dateUtil.dateAndTimeString(start)
runCalculation("onClickDate")
}
binding.date.setOnClickListener {
val cal = Calendar.getInstance()
cal.timeInMillis = start
DatePickerDialog(this, dateSetListener,
cal.get(Calendar.YEAR),
cal.get(Calendar.MONTH),
cal.get(Calendar.DAY_OF_MONTH)
).show()
}
val dm = DisplayMetrics()
windowManager?.defaultDisplay?.getMetrics(dm)
axisWidth = if (dm.densityDpi <= 120) 3 else if (dm.densityDpi <= 160) 10 else if (dm.densityDpi <= 320) 35 else if (dm.densityDpi <= 420) 50 else if (dm.densityDpi <= 560) 70 else 80
binding.bggraph.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.graphgrid)
binding.bggraph.gridLabelRenderer?.reloadStyles()
binding.bggraph.gridLabelRenderer?.labelVerticalWidth = axisWidth
overviewMenus.setupChartMenu(binding.chartMenuButton)
prepareGraphsIfNeeded(overviewMenus.setting.size)
savedInstanceState?.let { bundle ->
rangeToDisplay = bundle.getInt("rangeToDisplay", 0)
start = bundle.getLong("start", 0)
}
}
public override fun onPause() {
super.onPause()
disposable.clear()
iobCobCalculatorPluginHistory.stopCalculation("onPause")
}
@Synchronized
override fun onDestroy() {
destroyed = true
super.onDestroy()
}
public override fun onResume() {
super.onResume()
disposable.add(rxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
// catch only events from iobCobCalculatorPluginHistory
if (it.cause is EventCustomCalculationFinished) {
updateGUI("EventAutosensCalculationFinished", bgOnly = false)
}
}, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventIobCalculationProgress::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ binding.overviewIobcalculationprogess.text = it.progress }, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventRefreshOverview::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
if (it.now) {
updateGUI("EventRefreshOverview", bgOnly = false)
}
}, fabricPrivacy::logException)
)
if (start == 0L) {
// set start of current day
val calendar = Calendar.getInstance()
calendar.timeInMillis = System.currentTimeMillis()
calendar[Calendar.MILLISECOND] = 0
calendar[Calendar.SECOND] = 0
calendar[Calendar.MINUTE] = 0
calendar[Calendar.HOUR_OF_DAY] = 0
start = calendar.timeInMillis
runCalculation("onResume")
} else {
updateGUI("onResume", bgOnly = false)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("rangeToDisplay", rangeToDisplay)
outState.putLong("start", start)
}
private fun prepareGraphsIfNeeded(numOfGraphs: Int) {
synchronized(graphLock) {
if (numOfGraphs != secondaryGraphs.size - 1) {
//aapsLogger.debug("New secondary graph count ${numOfGraphs-1}")
// rebuild needed
secondaryGraphs.clear()
secondaryGraphsLabel.clear()
binding.iobGraph.removeAllViews()
for (i in 1 until numOfGraphs) {
val relativeLayout = RelativeLayout(this)
relativeLayout.layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
val graph = GraphView(this)
graph.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, resourceHelper.dpToPx(100)).also { it.setMargins(0, resourceHelper.dpToPx(15), 0, resourceHelper.dpToPx(10)) }
graph.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.graphgrid)
graph.gridLabelRenderer?.reloadStyles()
graph.gridLabelRenderer?.isHorizontalLabelsVisible = false
graph.gridLabelRenderer?.labelVerticalWidth = axisWidth
graph.gridLabelRenderer?.numVerticalLabels = 3
graph.viewport.backgroundColor = Color.argb(20, 255, 255, 255) // 8% of gray
relativeLayout.addView(graph)
val label = TextView(this)
val layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).also { it.setMargins(resourceHelper.dpToPx(30), resourceHelper.dpToPx(25), 0, 0) }
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP)
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
label.layoutParams = layoutParams
relativeLayout.addView(label)
secondaryGraphsLabel.add(label)
binding.iobGraph.addView(relativeLayout)
secondaryGraphs.add(graph)
}
}
}
}
private fun runCalculation(from: String) {
lifecycleScope.launch(Dispatchers.Default) {
val end = start + T.hours(rangeToDisplay.toLong()).msecs()
iobCobCalculatorPluginHistory.stopCalculation(from)
iobCobCalculatorPluginHistory.clearCache()
iobCobCalculatorPluginHistory.runCalculation(from, end, bgDataReload = true, limitDataToOldestAvailable = false, cause = eventCustomCalculationFinished)
}
}
@Synchronized
fun updateGUI(from: String, bgOnly: Boolean) {
val menuChartSettings = overviewMenus.setting
prepareGraphsIfNeeded(menuChartSettings.size)
aapsLogger.debug(LTag.UI, "updateGUI from: $from")
val pump = activePlugin.activePump
val profile = profileFunction.getProfile()
val lowLine = defaultValueHelper.determineLowLine()
val highLine = defaultValueHelper.determineHighLine()
lifecycleScope.launch(Dispatchers.Main) {
binding.noprofile.visibility = (profile == null).toVisibility()
profile ?: return@launch
if (destroyed) return@launch
binding.date.text = dateUtil.dateAndTimeString(start)
binding.zoom.text = rangeToDisplay.toString()
val graphData = GraphData(injector, binding.bggraph)
val secondaryGraphsData: ArrayList<GraphData> = ArrayList()
// do preparation in different thread
withContext(Dispatchers.Default) {
val fromTime: Long = start + T.secs(100).msecs()
val toTime: Long = start + T.hours(rangeToDisplay.toLong()).msecs() + T.secs(100).msecs()
aapsLogger.debug(LTag.UI, "Period: " + dateUtil.dateAndTimeString(fromTime) + " - " + dateUtil.dateAndTimeString(toTime))
val pointer = System.currentTimeMillis()
// **** In range Area ****
graphData.addInRangeArea(fromTime, toTime, lowLine, highLine)
// **** BG ****
// graphData.addBgReadings(fromTime, toTime, highLine, null)
// if (buildHelper.isDev()) graphData.addBucketedData(fromTime, toTime)
// add target line
// graphData.addTargetLine(fromTime, toTime, profile, null)
// **** NOW line ****
graphData.addNowLine(pointer)
if (!bgOnly) {
// Treatments
// graphData.addTreatments(fromTime, toTime)
if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal])
// graphData.addActivity(fromTime, toTime, false, 0.8)
// add basal data
if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal]) {
// graphData.addBasals(fromTime, toTime, lowLine / graphData.maxY / 1.2)
}
// ------------------ 2nd graph
synchronized(graphLock) {
for (g in 0 until secondaryGraphs.size) {
val secondGraphData = GraphData(injector, secondaryGraphs[g])
var useIobForScale = false
var useCobForScale = false
var useDevForScale = false
var useRatioForScale = false
var useDSForScale = false
var useBGIForScale = false
var useABSForScale = false
when {
menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal] -> useCobForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] -> useDevForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true
}
val alignIobScale = menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]
val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]
// if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(fromTime, toTime, useABSForScale, 1.0)
// if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(fromTime, toTime, useIobForScale, 1.0, menuChartSettings[g + 1][OverviewMenus.CharType.PRE.ordinal], alignIobScale)
// if (menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal]) secondGraphData.addCob(fromTime, toTime, useCobForScale, if (useCobForScale) 1.0 else 0.5)
// if (menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal]) secondGraphData.addDeviations(fromTime, toTime, useDevForScale, 1.0, alignDevBgiScale)
// if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(fromTime, toTime, useRatioForScale, 1.0)
// if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(fromTime, toTime, useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8, alignDevBgiScale)
// if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && buildHelper.isDev()) secondGraphData.addDeviationSlope(fromTime, toTime, useDSForScale, 1.0)
// set manual x bounds to have nice steps
secondGraphData.formatAxis(fromTime, toTime)
secondGraphData.addNowLine(pointer)
secondaryGraphsData.add(secondGraphData)
}
}
}
// set manual x bounds to have nice steps
graphData.setNumVerticalLabels()
graphData.formatAxis(fromTime, toTime)
}
// finally enforce drawing of graphs in UI thread
graphData.performUpdate()
if (!bgOnly)
synchronized(graphLock) {
for (g in 0 until secondaryGraphs.size) {
secondaryGraphsLabel[g].text = overviewMenus.enabledTypes(g + 1)
secondaryGraphs[g].visibility = (!bgOnly && (
menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] ||
menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal] ||
menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] ||
menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] ||
menuChartSettings[g + 1][OverviewMenus.CharType.ACT.ordinal] ||
menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] ||
menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal]
)).toVisibility()
secondaryGraphsData[g].performUpdate()
}
}
}
}
}

View file

@ -1,42 +0,0 @@
package info.nightscout.androidaps.historyBrowser
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.sharedPreferences.SP
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class IobCobCalculatorPluginHistory @Inject constructor(
injector: HasAndroidInjector,
aapsLogger: AAPSLogger,
aapsSchedulers: AapsSchedulers,
rxBus: RxBusWrapper,
sp: SP,
resourceHelper: ResourceHelper,
profileFunction: ProfileFunction,
activePlugin: ActivePlugin,
sensitivityOref1Plugin: SensitivityOref1Plugin,
sensitivityAAPSPlugin: SensitivityAAPSPlugin,
sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin,
fabricPrivacy: FabricPrivacy,
dateUtil: DateUtil,
repository: AppRepository
) : IobCobCalculatorPlugin(injector, aapsLogger, aapsSchedulers, rxBus, sp, resourceHelper, profileFunction,
activePlugin, sensitivityOref1Plugin, sensitivityAAPSPlugin, sensitivityWeightedAveragePlugin, fabricPrivacy, dateUtil, repository) {
override fun onStart() { // do not attach to rxbus
}
}

View file

@ -28,7 +28,7 @@ import info.nightscout.androidaps.events.EventTherapyEventChange
import info.nightscout.androidaps.extensions.toStringMedium
import info.nightscout.androidaps.extensions.toStringShort
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity
import info.nightscout.androidaps.activities.HistoryBrowseActivity
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.CommandQueueProvider
import info.nightscout.androidaps.interfaces.Config

View file

@ -1,46 +1,64 @@
package info.nightscout.androidaps.plugins.general.overview
import android.graphics.DashPathEffect
import android.graphics.Paint
import com.jjoe64.graphview.series.BarGraphSeries
import com.jjoe64.graphview.series.DataPoint
import com.jjoe64.graphview.series.LineGraphSeries
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.IobTotal
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.database.entities.ExtendedBolus
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.database.entities.TemporaryBasal
import info.nightscout.androidaps.database.entities.TemporaryTarget
import info.nightscout.androidaps.extensions.convertedToPercent
import info.nightscout.androidaps.extensions.target
import info.nightscout.androidaps.extensions.toStringFull
import info.nightscout.androidaps.extensions.toStringShort
import info.nightscout.androidaps.extensions.valueToUnits
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.FixedLineGraphSeries
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.Scale
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.ScaledDataPoint
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.*
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.CobInfo
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.collections.ArrayList
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
@Singleton
class OverviewData @Inject constructor(
private val injector: HasAndroidInjector,
private val aapsLogger: AAPSLogger,
private val resourceHelper: ResourceHelper,
private val dateUtil: DateUtil,
private val sp: SP,
private val activePlugin: ActivePlugin,
private val defaultValueHelper: DefaultValueHelper,
private val profileFunction: ProfileFunction
private val profileFunction: ProfileFunction,
private val config: Config,
private val loopPlugin: LoopPlugin,
private val nsDeviceStatus: NSDeviceStatus,
private val repository: AppRepository,
private val overviewMenus: OverviewMenus,
private val iobCobCalculator: IobCobCalculator,
private val translator: Translator
) {
enum class Property {
@ -84,7 +102,7 @@ class OverviewData @Inject constructor(
var profileName: String? = null
var profileNameWithRemainingTime: String? = null
val profileBackgroudColor: Int
val profileBackgroundColor: Int
get() =
profile?.let { profile ->
if (profile.percentage != 100 || profile.timeshift != 0) resourceHelper.gc(R.color.ribbonWarning)
@ -211,7 +229,7 @@ class OverviewData @Inject constructor(
* TEMP TARGET
*/
var temporarytarget: TemporaryTarget? = null
var temporaryTarget: TemporaryTarget? = null
/*
* SENSITIVITY
@ -278,4 +296,515 @@ class OverviewData @Inject constructor(
var dsMaxSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
var dsMinSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
@Synchronized
@Suppress("SameParameterValue", "UNUSED_PARAMETER")
fun prepareBgData(from: String) {
// val start = dateUtil.now()
maxBgValue = Double.MIN_VALUE
bgReadingsArray = repository.compatGetBgReadingsDataFromTime(fromTime, toTime, false).blockingGet()
val bgListArray: MutableList<DataPointWithLabelInterface> = java.util.ArrayList()
for (bg in bgReadingsArray) {
if (bg.timestamp < fromTime || bg.timestamp > toTime) continue
if (bg.value > maxBgValue) maxBgValue = bg.value
bgListArray.add(GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper))
}
bgReadingGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })
maxBgValue = Profile.fromMgdlToUnits(maxBgValue, profileFunction.getUnits())
if (defaultValueHelper.determineHighLine() > maxBgValue) maxBgValue = defaultValueHelper.determineHighLine()
maxBgValue = addUpperChartMargin(maxBgValue)
// profiler.log(LTag.UI, "prepareBgData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
fun preparePredictions(from: String) {
// val start = dateUtil.now()
val apsResult = if (config.APS) loopPlugin.lastRun?.constraintsProcessed else nsDeviceStatus.getAPSResult(injector)
val predictionsAvailable = if (config.APS) loopPlugin.lastRun?.request?.hasPredictions == true else config.NSCLIENT
val menuChartSettings = overviewMenus.setting
// align to hours
val calendar = Calendar.getInstance().also {
it.timeInMillis = System.currentTimeMillis()
it[Calendar.MILLISECOND] = 0
it[Calendar.SECOND] = 0
it[Calendar.MINUTE] = 0
it.add(Calendar.HOUR, 1)
}
if (predictionsAvailable && apsResult != null && menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) {
var predictionHours = (ceil(apsResult.latestPredictionsTime - System.currentTimeMillis().toDouble()) / (60 * 60 * 1000)).toInt()
predictionHours = min(2, predictionHours)
predictionHours = max(0, predictionHours)
val hoursToFetch = rangeToDisplay - predictionHours
toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific
fromTime = toTime - T.hours(hoursToFetch.toLong()).msecs()
endTime = toTime + T.hours(predictionHours.toLong()).msecs()
} else {
toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific
fromTime = toTime - T.hours(rangeToDisplay.toLong()).msecs()
endTime = toTime
}
val bgListArray: MutableList<DataPointWithLabelInterface> = java.util.ArrayList()
val predictions: MutableList<GlucoseValueDataPoint>? = apsResult?.predictions
?.map { bg -> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper) }
?.toMutableList()
if (predictions != null) {
predictions.sortWith { o1: GlucoseValueDataPoint, o2: GlucoseValueDataPoint -> o1.x.compareTo(o2.x) }
for (prediction in predictions) if (prediction.data.value >= 40) bgListArray.add(prediction)
}
predictionsGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })
// profiler.log(LTag.UI, "preparePredictions() $from", start)
}
@Synchronized
@Suppress("SameParameterValue", "UNUSED_PARAMETER")
fun prepareBucketedData(from: String) {
// val start = dateUtil.now()
val bucketedData = iobCobCalculator.ads.getBucketedDataTableCopy() ?: return
if (bucketedData.isEmpty()) {
aapsLogger.debug("No bucketed data.")
return
}
val bucketedListArray: MutableList<DataPointWithLabelInterface> = java.util.ArrayList()
for (inMemoryGlucoseValue in bucketedData) {
if (inMemoryGlucoseValue.timestamp < fromTime || inMemoryGlucoseValue.timestamp > toTime) continue
bucketedListArray.add(InMemoryGlucoseValueDataPoint(inMemoryGlucoseValue, profileFunction, resourceHelper))
}
bucketedGraphSeries = PointsWithLabelGraphSeries(Array(bucketedListArray.size) { i -> bucketedListArray[i] })
// profiler.log(LTag.UI, "prepareBucketedData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
fun prepareBasalData(from: String) {
// val start = dateUtil.now()
maxBasalValueFound = 0.0
val baseBasalArray: MutableList<ScaledDataPoint> = java.util.ArrayList()
val tempBasalArray: MutableList<ScaledDataPoint> = java.util.ArrayList()
val basalLineArray: MutableList<ScaledDataPoint> = java.util.ArrayList()
val absoluteBasalLineArray: MutableList<ScaledDataPoint> = java.util.ArrayList()
var lastLineBasal = 0.0
var lastAbsoluteLineBasal = -1.0
var lastBaseBasal = 0.0
var lastTempBasal = 0.0
var time = fromTime
while (time < toTime) {
val profile = profileFunction.getProfile(time)
if (profile == null) {
time += 60 * 1000L
continue
}
val basalData = iobCobCalculator.getBasalData(profile, time)
val baseBasalValue = basalData.basal
var absoluteLineValue = baseBasalValue
var tempBasalValue = 0.0
var basal = 0.0
if (basalData.isTempBasalRunning) {
tempBasalValue = basalData.tempBasalAbsolute
absoluteLineValue = tempBasalValue
if (tempBasalValue != lastTempBasal) {
tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale))
tempBasalArray.add(ScaledDataPoint(time, tempBasalValue.also { basal = it }, basalScale))
}
if (lastBaseBasal != 0.0) {
baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale))
baseBasalArray.add(ScaledDataPoint(time, 0.0, basalScale))
lastBaseBasal = 0.0
}
} else {
if (baseBasalValue != lastBaseBasal) {
baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale))
baseBasalArray.add(ScaledDataPoint(time, baseBasalValue.also { basal = it }, basalScale))
lastBaseBasal = baseBasalValue
}
if (lastTempBasal != 0.0) {
tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale))
tempBasalArray.add(ScaledDataPoint(time, 0.0, basalScale))
}
}
if (baseBasalValue != lastLineBasal) {
basalLineArray.add(ScaledDataPoint(time, lastLineBasal, basalScale))
basalLineArray.add(ScaledDataPoint(time, baseBasalValue, basalScale))
}
if (absoluteLineValue != lastAbsoluteLineBasal) {
absoluteBasalLineArray.add(ScaledDataPoint(time, lastAbsoluteLineBasal, basalScale))
absoluteBasalLineArray.add(ScaledDataPoint(time, basal, basalScale))
}
lastAbsoluteLineBasal = absoluteLineValue
lastLineBasal = baseBasalValue
lastTempBasal = tempBasalValue
maxBasalValueFound = max(maxBasalValueFound, max(tempBasalValue, baseBasalValue))
time += 60 * 1000L
}
// final points
basalLineArray.add(ScaledDataPoint(toTime, lastLineBasal, basalScale))
baseBasalArray.add(ScaledDataPoint(toTime, lastBaseBasal, basalScale))
tempBasalArray.add(ScaledDataPoint(toTime, lastTempBasal, basalScale))
absoluteBasalLineArray.add(ScaledDataPoint(toTime, lastAbsoluteLineBasal, basalScale))
// create series
baseBasalGraphSeries = LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = resourceHelper.gc(R.color.basebasal)
it.thickness = 0
}
tempBasalGraphSeries = LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = resourceHelper.gc(R.color.tempbasal)
it.thickness = 0
}
basalLineGraphSeries = LineGraphSeries(Array(basalLineArray.size) { i -> basalLineArray[i] }).also {
it.setCustomPaint(Paint().also { paint ->
paint.style = Paint.Style.STROKE
paint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2
paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f)
paint.color = resourceHelper.gc(R.color.basal)
})
}
absoluteBasalGraphSeries = LineGraphSeries(Array(absoluteBasalLineArray.size) { i -> absoluteBasalLineArray[i] }).also {
it.setCustomPaint(Paint().also { absolutePaint ->
absolutePaint.style = Paint.Style.STROKE
absolutePaint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2
absolutePaint.color = resourceHelper.gc(R.color.basal)
})
}
// profiler.log(LTag.UI, "prepareBasalData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
fun prepareTemporaryTargetData(from: String) {
// val start = dateUtil.now()
val profile = profile ?: return
val units = profileFunction.getUnits()
var toTime = toTime
val targetsSeriesArray: MutableList<DataPoint> = java.util.ArrayList()
var lastTarget = -1.0
loopPlugin.lastRun?.constraintsProcessed?.let { toTime = max(it.latestPredictionsTime, toTime) }
var time = fromTime
while (time < toTime) {
val tt = repository.getTemporaryTargetActiveAt(time).blockingGet()
val value: Double = if (tt is ValueWrapper.Existing) {
Profile.fromMgdlToUnits(tt.value.target(), units)
} else {
Profile.fromMgdlToUnits((profile.getTargetLowMgdl(time) + profile.getTargetHighMgdl(time)) / 2, units)
}
if (lastTarget != value) {
if (lastTarget != -1.0) targetsSeriesArray.add(DataPoint(time.toDouble(), lastTarget))
targetsSeriesArray.add(DataPoint(time.toDouble(), value))
}
lastTarget = value
time += 5 * 60 * 1000L
}
// final point
targetsSeriesArray.add(DataPoint(toTime.toDouble(), lastTarget))
// create series
temporaryTargetSeries = LineGraphSeries(Array(targetsSeriesArray.size) { i -> targetsSeriesArray[i] }).also {
it.isDrawBackground = false
it.color = resourceHelper.gc(R.color.tempTargetBackground)
it.thickness = 2
}
// profiler.log(LTag.UI, "prepareTemporaryTargetData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
fun prepareTreatmentsData(from: String) {
// val start = dateUtil.now()
maxTreatmentsValue = 0.0
val filteredTreatments: MutableList<DataPointWithLabelInterface> = java.util.ArrayList()
repository.getBolusesIncludingInvalidFromTimeToTime(fromTime, endTime, true).blockingGet()
.map { BolusDataPoint(it, resourceHelper, activePlugin, defaultValueHelper) }
.filter { it.data.type != Bolus.Type.SMB || it.data.isValid }
.forEach {
it.y = getNearestBg(it.x.toLong())
filteredTreatments.add(it)
}
repository.getCarbsIncludingInvalidFromTimeToTimeExpanded(fromTime, endTime, true).blockingGet()
.map { CarbsDataPoint(it, resourceHelper) }
.forEach {
it.y = getNearestBg(it.x.toLong())
filteredTreatments.add(it)
}
// ProfileSwitch
repository.getEffectiveProfileSwitchDataFromTimeToTime(fromTime, endTime, true).blockingGet()
.map { EffectiveProfileSwitchDataPoint(it) }
.forEach(filteredTreatments::add)
// Extended bolus
if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) {
repository.getExtendedBolusDataFromTimeToTime(fromTime, endTime, true).blockingGet()
.map { ExtendedBolusDataPoint(it) }
.filter { it.duration != 0L }
.forEach {
it.y = getNearestBg(it.x.toLong())
filteredTreatments.add(it)
}
}
// Careportal
repository.compatGetTherapyEventDataFromToTime(fromTime - T.hours(6).msecs(), endTime).blockingGet()
.map { TherapyEventDataPoint(it, resourceHelper, profileFunction, translator) }
.filterTimeframe(fromTime, endTime)
.forEach {
if (it.y == 0.0) it.y = getNearestBg(it.x.toLong())
filteredTreatments.add(it)
}
// increase maxY if a treatment forces it's own height that's higher than a BG value
filteredTreatments.map { it.y }
.maxOrNull()
?.let(::addUpperChartMargin)
?.let { maxTreatmentsValue = maxOf(maxTreatmentsValue, it) }
treatmentsSeries = PointsWithLabelGraphSeries(filteredTreatments.toTypedArray())
// profiler.log(LTag.UI, "prepareTreatmentsData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
fun prepareIobAutosensData(from: String) {
// val start = dateUtil.now()
val iobArray: MutableList<ScaledDataPoint> = java.util.ArrayList()
val absIobArray: MutableList<ScaledDataPoint> = java.util.ArrayList()
maxIobValueFound = Double.MIN_VALUE
var lastIob = 0.0
var absLastIob = 0.0
var time = fromTime
val minFailOverActiveList: MutableList<DataPointWithLabelInterface> = java.util.ArrayList()
val cobArray: MutableList<ScaledDataPoint> = java.util.ArrayList()
maxCobValueFound = Double.MIN_VALUE
var lastCob = 0
val actArrayHist: MutableList<ScaledDataPoint> = java.util.ArrayList()
val actArrayPrediction: MutableList<ScaledDataPoint> = java.util.ArrayList()
val now = dateUtil.now().toDouble()
maxIAValue = 0.0
val bgiArrayHist: MutableList<ScaledDataPoint> = java.util.ArrayList()
val bgiArrayPrediction: MutableList<ScaledDataPoint> = java.util.ArrayList()
maxBGIValue = Double.MIN_VALUE
val devArray: MutableList<OverviewPlugin.DeviationDataPoint> = java.util.ArrayList()
maxDevValueFound = Double.MIN_VALUE
val ratioArray: MutableList<ScaledDataPoint> = java.util.ArrayList()
maxRatioValueFound = 5.0 //even if sens data equals 0 for all the period, minimum scale is between 95% and 105%
minRatioValueFound = -5.0
val dsMaxArray: MutableList<ScaledDataPoint> = java.util.ArrayList()
val dsMinArray: MutableList<ScaledDataPoint> = java.util.ArrayList()
maxFromMaxValueFound = Double.MIN_VALUE
maxFromMinValueFound = Double.MIN_VALUE
val adsData = iobCobCalculator.ads.clone()
while (time <= toTime) {
val profile = profileFunction.getProfile(time)
if (profile == null) {
time += 5 * 60 * 1000L
continue
}
// IOB
val iob = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile)
val baseBasalIob = iobCobCalculator.calculateAbsoluteIobFromBaseBasals(time)
val absIob = IobTotal.combine(iob, baseBasalIob)
val autosensData = adsData.getAutosensDataAtTime(time)
if (abs(lastIob - iob.iob) > 0.02) {
if (abs(lastIob - iob.iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale))
iobArray.add(ScaledDataPoint(time, iob.iob, iobScale))
maxIobValueFound = maxOf(maxIobValueFound, abs(iob.iob))
lastIob = iob.iob
}
if (abs(absLastIob - absIob.iob) > 0.02) {
if (abs(absLastIob - absIob.iob) > 0.2) absIobArray.add(ScaledDataPoint(time, absLastIob, iobScale))
absIobArray.add(ScaledDataPoint(time, absIob.iob, iobScale))
maxIobValueFound = maxOf(maxIobValueFound, abs(absIob.iob))
absLastIob = absIob.iob
}
// COB
if (autosensData != null) {
val cob = autosensData.cob.toInt()
if (cob != lastCob) {
if (autosensData.carbsFromBolus > 0) cobArray.add(ScaledDataPoint(time, lastCob.toDouble(), cobScale))
cobArray.add(ScaledDataPoint(time, cob.toDouble(), cobScale))
maxCobValueFound = max(maxCobValueFound, cob.toDouble())
lastCob = cob
}
if (autosensData.failoverToMinAbsorbtionRate) {
autosensData.setScale(cobScale)
autosensData.setChartTime(time)
minFailOverActiveList.add(autosensData)
}
}
// ACTIVITY
if (time <= now) actArrayHist.add(ScaledDataPoint(time, iob.activity, actScale))
else actArrayPrediction.add(ScaledDataPoint(time, iob.activity, actScale))
maxIAValue = max(maxIAValue, abs(iob.activity))
// BGI
val devBgiScale = overviewMenus.isEnabledIn(OverviewMenus.CharType.DEV) == overviewMenus.isEnabledIn(OverviewMenus.CharType.BGI)
val deviation = if (devBgiScale) autosensData?.deviation ?: 0.0 else 0.0
val bgi: Double = iob.activity * profile.getIsfMgdl(time) * 5.0
if (time <= now) bgiArrayHist.add(ScaledDataPoint(time, bgi, bgiScale))
else bgiArrayPrediction.add(ScaledDataPoint(time, bgi, bgiScale))
maxBGIValue = max(maxBGIValue, max(abs(bgi), deviation))
// DEVIATIONS
if (autosensData != null) {
var color = resourceHelper.gc(R.color.deviationblack) // "="
if (autosensData.type == "" || autosensData.type == "non-meal") {
if (autosensData.pastSensitivity == "C") color = resourceHelper.gc(R.color.deviationgrey)
if (autosensData.pastSensitivity == "+") color = resourceHelper.gc(R.color.deviationgreen)
if (autosensData.pastSensitivity == "-") color = resourceHelper.gc(R.color.deviationred)
} else if (autosensData.type == "uam") {
color = resourceHelper.gc(R.color.uam)
} else if (autosensData.type == "csf") {
color = resourceHelper.gc(R.color.deviationgrey)
}
devArray.add(OverviewPlugin.DeviationDataPoint(time.toDouble(), autosensData.deviation, color, devScale))
maxDevValueFound = maxOf(maxDevValueFound, abs(autosensData.deviation), abs(bgi))
}
// RATIO
if (autosensData != null) {
ratioArray.add(ScaledDataPoint(time, 100.0 * (autosensData.autosensResult.ratio - 1), ratioScale))
maxRatioValueFound = max(maxRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1))
minRatioValueFound = min(minRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1))
}
// DEV SLOPE
if (autosensData != null) {
dsMaxArray.add(ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, dsMaxScale))
dsMinArray.add(ScaledDataPoint(time, autosensData.slopeFromMinDeviation, dsMinScale))
maxFromMaxValueFound = max(maxFromMaxValueFound, abs(autosensData.slopeFromMaxDeviation))
maxFromMinValueFound = max(maxFromMinValueFound, abs(autosensData.slopeFromMinDeviation))
}
time += 5 * 60 * 1000L
}
// IOB
iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50%
it.color = resourceHelper.gc(R.color.iob)
it.thickness = 3
}
absIobSeries = FixedLineGraphSeries(Array(absIobArray.size) { i -> absIobArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50%
it.color = resourceHelper.gc(R.color.iob)
it.thickness = 3
}
if (overviewMenus.setting[0][OverviewMenus.CharType.PRE.ordinal]) {
val autosensData = adsData.getLastAutosensData("GraphData", aapsLogger, dateUtil)
val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult()
val isTempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing
val iobPrediction: MutableList<DataPointWithLabelInterface> = java.util.ArrayList()
val iobPredictionArray = iobCobCalculator.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
for (i in iobPredictionArray) {
iobPrediction.add(i.setColor(resourceHelper.gc(R.color.iobPredAS)))
maxIobValueFound = max(maxIobValueFound, abs(i.iob))
}
iobPredictions1Series = PointsWithLabelGraphSeries(Array(iobPrediction.size) { i -> iobPrediction[i] })
val iobPrediction2: MutableList<DataPointWithLabelInterface> = java.util.ArrayList()
val iobPredictionArray2 = iobCobCalculator.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
for (i in iobPredictionArray2) {
iobPrediction2.add(i.setColor(resourceHelper.gc(R.color.iobPred)))
maxIobValueFound = max(maxIobValueFound, abs(i.iob))
}
iobPredictions2Series = PointsWithLabelGraphSeries(Array(iobPrediction2.size) { i -> iobPrediction2[i] })
aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray))
aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray2))
} else {
iobPredictions1Series = PointsWithLabelGraphSeries()
iobPredictions2Series = PointsWithLabelGraphSeries()
}
// COB
cobSeries = FixedLineGraphSeries(Array(cobArray.size) { i -> cobArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.cob) //50%
it.color = resourceHelper.gc(R.color.cob)
it.thickness = 3
}
cobMinFailOverSeries = PointsWithLabelGraphSeries(Array(minFailOverActiveList.size) { i -> minFailOverActiveList[i] })
// ACTIVITY
activitySeries = FixedLineGraphSeries(Array(actArrayHist.size) { i -> actArrayHist[i] }).also {
it.isDrawBackground = false
it.color = resourceHelper.gc(R.color.activity)
it.thickness = 3
}
activityPredictionSeries = FixedLineGraphSeries(Array(actArrayPrediction.size) { i -> actArrayPrediction[i] }).also {
it.setCustomPaint(Paint().also { paint ->
paint.style = Paint.Style.STROKE
paint.strokeWidth = 3f
paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f)
paint.color = resourceHelper.gc(R.color.activity)
})
}
// BGI
minusBgiSeries = FixedLineGraphSeries(Array(bgiArrayHist.size) { i -> bgiArrayHist[i] }).also {
it.isDrawBackground = false
it.color = resourceHelper.gc(R.color.bgi)
it.thickness = 3
}
minusBgiHistSeries = FixedLineGraphSeries(Array(bgiArrayPrediction.size) { i -> bgiArrayPrediction[i] }).also {
it.setCustomPaint(Paint().also { paint ->
paint.style = Paint.Style.STROKE
paint.strokeWidth = 3f
paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f)
paint.color = resourceHelper.gc(R.color.bgi)
})
}
// DEVIATIONS
deviationsSeries = BarGraphSeries(Array(devArray.size) { i -> devArray[i] }).also {
it.setValueDependentColor { data: OverviewPlugin.DeviationDataPoint -> data.color }
}
// RATIO
ratioSeries = LineGraphSeries(Array(ratioArray.size) { i -> ratioArray[i] }).also {
it.color = resourceHelper.gc(R.color.ratio)
it.thickness = 3
}
// DEV SLOPE
dsMaxSeries = LineGraphSeries(Array(dsMaxArray.size) { i -> dsMaxArray[i] }).also {
it.color = resourceHelper.gc(R.color.devslopepos)
it.thickness = 3
}
dsMinSeries = LineGraphSeries(Array(dsMinArray.size) { i -> dsMinArray[i] }).also {
it.color = resourceHelper.gc(R.color.devslopeneg)
it.thickness = 3
}
// profiler.log(LTag.UI, "prepareIobAutosensData() $from", start)
}
private fun addUpperChartMargin(maxBgValue: Double) =
if (profileFunction.getUnits() == GlucoseUnit.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4
private fun getNearestBg(date: Long): Double {
bgReadingsArray.let { bgReadingsArray ->
for (reading in bgReadingsArray) {
if (reading.timestamp > date) continue
return Profile.fromMgdlToUnits(reading.value, profileFunction.getUnits())
}
return if (bgReadingsArray.isNotEmpty()) Profile.fromMgdlToUnits(bgReadingsArray[0].value, profileFunction.getUnits())
else Profile.fromMgdlToUnits(100.0, profileFunction.getUnits())
}
}
private fun <E : DataPointWithLabelInterface> List<E>.filterTimeframe(fromTime: Long, endTime: Long): List<E> =
filter { it.x + it.duration >= fromTime && it.x <= endTime }
}

View file

@ -57,7 +57,6 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProv
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
import info.nightscout.androidaps.plugins.source.DexcomPlugin
import info.nightscout.androidaps.plugins.source.XdripPlugin
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import info.nightscout.androidaps.queue.CommandQueue
import info.nightscout.androidaps.skins.SkinProvider
import info.nightscout.androidaps.utils.*
@ -92,7 +91,6 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
@Inject lateinit var nsDeviceStatus: NSDeviceStatus
@Inject lateinit var loopPlugin: LoopPlugin
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var treatmentsPlugin: TreatmentsPlugin
@Inject lateinit var iobCobCalculator: IobCobCalculator
@Inject lateinit var dexcomPlugin: DexcomPlugin
@Inject lateinit var dexcomMediator: DexcomPlugin.DexcomMediator
@ -108,7 +106,6 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
@Inject lateinit var trendCalculator: TrendCalculator
@Inject lateinit var config: Config
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var databaseHelper: DatabaseHelperInterface
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var repository: AppRepository
@Inject lateinit var glucoseStatusProvider: GlucoseStatusProvider
@ -640,7 +637,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
OverviewData.Property.PROFILE -> {
binding.loopPumpStatusLayout.activeProfile.text = overviewData.profileNameWithRemainingTime
?: ""
binding.loopPumpStatusLayout.activeProfile.setBackgroundColor(overviewData.profileBackgroudColor)
binding.loopPumpStatusLayout.activeProfile.setBackgroundColor(overviewData.profileBackgroundColor)
binding.loopPumpStatusLayout.activeProfile.setTextColor(overviewData.profileTextColor)
}
@ -705,7 +702,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
OverviewData.Property.TEMPORARY_TARGET -> {
// temp target
val tempTarget = overviewData.temporarytarget
val tempTarget = overviewData.temporaryTarget
if (tempTarget != null) {
binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning))
binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning))
@ -730,7 +727,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
}
OverviewData.Property.GRAPH -> {
val graphData = GraphData(injector, binding.graphsLayout.bgGraph)
val graphData = GraphData(injector, binding.graphsLayout.bgGraph, overviewData)
val menuChartSettings = overviewMenus.setting
graphData.addInRangeArea(overviewData.fromTime, overviewData.endTime, defaultValueHelper.determineLowLine(), defaultValueHelper.determineHighLine())
graphData.addBgReadings(menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal])
@ -755,7 +752,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
val now = System.currentTimeMillis()
for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) {
val secondGraphData = GraphData(injector, secondaryGraphs[g])
val secondGraphData = GraphData(injector, secondaryGraphs[g], overviewData)
var useABSForScale = false
var useIobForScale = false
var useCobForScale = false

View file

@ -1,50 +1,37 @@
package info.nightscout.androidaps.plugins.general.overview
import android.graphics.DashPathEffect
import android.graphics.Paint
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import com.jjoe64.graphview.series.BarGraphSeries
import com.jjoe64.graphview.series.DataPoint
import com.jjoe64.graphview.series.LineGraphSeries
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.IobTotal
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.events.*
import info.nightscout.androidaps.extensions.*
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.aps.events.EventLoopInvoked
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverview
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.*
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.Scale
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.ScaledDataPoint
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventBucketedDataCreated
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.Translator
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import org.json.JSONObject
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
@Singleton
class OverviewPlugin @Inject constructor(
@ -63,10 +50,6 @@ class OverviewPlugin @Inject constructor(
private val profileFunction: ProfileFunction,
private val iobCobCalculator: IobCobCalculator,
private val repository: AppRepository,
private val defaultValueHelper: DefaultValueHelper,
private val loopPlugin: LoopPlugin,
private val activePlugin: ActivePlugin,
private val nsDeviceStatus: NSDeviceStatus,
private val overviewData: OverviewData,
private val overviewMenus: OverviewMenus
) : PluginBase(PluginDescription()
@ -133,28 +116,28 @@ class OverviewPlugin @Inject constructor(
.observeOn(aapsSchedulers.io)
.subscribe({
loadIobCobResults("EventTreatmentChange")
prepareTreatmentsData("EventTreatmentChange")
overviewData.prepareTreatmentsData("EventTreatmentChange")
overviewBus.send(EventUpdateOverview("EventTreatmentChange", OverviewData.Property.GRAPH))
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTherapyEventChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
prepareTreatmentsData("EventTherapyEventChange")
overviewData.prepareTreatmentsData("EventTherapyEventChange")
overviewBus.send(EventUpdateOverview("EventTherapyEventChange", OverviewData.Property.GRAPH))
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventBucketedDataCreated::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
prepareBucketedData("EventBucketedDataCreated")
prepareBgData("EventBucketedDataCreated")
overviewData.prepareBucketedData("EventBucketedDataCreated")
overviewData.prepareBgData("EventBucketedDataCreated")
overviewBus.send(EventUpdateOverview("EventBucketedDataCreated", OverviewData.Property.GRAPH))
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventLoopInvoked::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ preparePredictions("EventLoopInvoked") }, fabricPrivacy::logException)
.subscribe({ overviewData.preparePredictions("EventLoopInvoked") }, fabricPrivacy::logException)
disposable.add(rxBus
.toObservable(EventProfileSwitchChanged::class.java)
.observeOn(aapsSchedulers.io)
@ -162,7 +145,9 @@ class OverviewPlugin @Inject constructor(
disposable.add(rxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ refreshLoop("EventAutosensCalculationFinished") }, fabricPrivacy::logException))
.subscribe({
if (it.cause !is EventCustomCalculationFinished) refreshLoop("EventAutosensCalculationFinished")
}, fabricPrivacy::logException))
Thread { loadAll("onResume") }.start()
}
@ -239,7 +224,8 @@ class OverviewPlugin @Inject constructor(
.storeDouble(R.string.key_statuslights_bat_critical, sp, resourceHelper)
}
@Volatile var runningRefresh = false
@Volatile
var runningRefresh = false
override fun refreshLoop(from: String) {
if (runningRefresh) return
runningRefresh = true
@ -252,11 +238,11 @@ class OverviewPlugin @Inject constructor(
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_TARGET))
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.SENSITIVITY))
loadAsData(from)
preparePredictions(from)
prepareBasalData(from)
prepareTemporaryTargetData(from)
prepareTreatmentsData(from)
prepareIobAutosensData(from)
overviewData.preparePredictions(from)
overviewData.prepareBasalData(from)
overviewData.prepareTemporaryTargetData(from)
overviewData.prepareTreatmentsData(from)
overviewData.prepareIobAutosensData(from)
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.GRAPH))
aapsLogger.debug(LTag.UI, "refreshLoop finished")
runningRefresh = false
@ -271,9 +257,9 @@ class OverviewPlugin @Inject constructor(
loadTemporaryTarget(from)
loadIobCobResults(from)
loadAsData(from)
prepareBasalData(from)
prepareTemporaryTargetData(from)
prepareTreatmentsData(from)
overviewData.prepareBasalData(from)
overviewData.prepareTemporaryTargetData(from)
overviewData.prepareTreatmentsData(from)
// prepareIobAutosensData(from)
// preparePredictions(from)
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.GRAPH))
@ -299,8 +285,8 @@ class OverviewPlugin @Inject constructor(
private fun loadTemporaryTarget(from: String) {
val tempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
if (tempTarget is ValueWrapper.Existing) overviewData.temporarytarget = tempTarget.value
else overviewData.temporarytarget = null
if (tempTarget is ValueWrapper.Existing) overviewData.temporaryTarget = tempTarget.value
else overviewData.temporaryTarget = null
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_TARGET))
}
@ -326,514 +312,4 @@ class OverviewPlugin @Inject constructor(
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.IOB_COB))
}
@Synchronized
@Suppress("SameParameterValue", "UNUSED_PARAMETER")
private fun prepareBgData(from: String) {
// val start = dateUtil.now()
var maxBgValue = Double.MIN_VALUE
overviewData.bgReadingsArray = repository.compatGetBgReadingsDataFromTime(overviewData.fromTime, overviewData.toTime, false).blockingGet()
val bgListArray: MutableList<DataPointWithLabelInterface> = ArrayList()
for (bg in overviewData.bgReadingsArray) {
if (bg.timestamp < overviewData.fromTime || bg.timestamp > overviewData.toTime) continue
if (bg.value > maxBgValue) maxBgValue = bg.value
bgListArray.add(GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper))
}
overviewData.bgReadingGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })
overviewData.maxBgValue = Profile.fromMgdlToUnits(maxBgValue, profileFunction.getUnits())
if (defaultValueHelper.determineHighLine() > maxBgValue) overviewData.maxBgValue = defaultValueHelper.determineHighLine()
overviewData.maxBgValue = addUpperChartMargin(overviewData.maxBgValue)
// profiler.log(LTag.UI, "prepareBgData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
private fun preparePredictions(from: String) {
// val start = dateUtil.now()
val apsResult = if (config.APS) loopPlugin.lastRun?.constraintsProcessed else nsDeviceStatus.getAPSResult(injector)
val predictionsAvailable = if (config.APS) loopPlugin.lastRun?.request?.hasPredictions == true else config.NSCLIENT
val menuChartSettings = overviewMenus.setting
// align to hours
val calendar = Calendar.getInstance().also {
it.timeInMillis = System.currentTimeMillis()
it[Calendar.MILLISECOND] = 0
it[Calendar.SECOND] = 0
it[Calendar.MINUTE] = 0
it.add(Calendar.HOUR, 1)
}
if (predictionsAvailable && apsResult != null && menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) {
var predictionHours = (ceil(apsResult.latestPredictionsTime - System.currentTimeMillis().toDouble()) / (60 * 60 * 1000)).toInt()
predictionHours = min(2, predictionHours)
predictionHours = max(0, predictionHours)
val hoursToFetch = overviewData.rangeToDisplay - predictionHours
overviewData.toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific
overviewData.fromTime = overviewData.toTime - T.hours(hoursToFetch.toLong()).msecs()
overviewData.endTime = overviewData.toTime + T.hours(predictionHours.toLong()).msecs()
} else {
overviewData.toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific
overviewData.fromTime = overviewData.toTime - T.hours(overviewData.rangeToDisplay.toLong()).msecs()
overviewData.endTime = overviewData.toTime
}
val bgListArray: MutableList<DataPointWithLabelInterface> = ArrayList()
val predictions: MutableList<GlucoseValueDataPoint>? = apsResult?.predictions
?.map { bg -> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper) }
?.toMutableList()
if (predictions != null) {
predictions.sortWith { o1: GlucoseValueDataPoint, o2: GlucoseValueDataPoint -> o1.x.compareTo(o2.x) }
for (prediction in predictions) if (prediction.data.value >= 40) bgListArray.add(prediction)
}
overviewData.predictionsGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })
// profiler.log(LTag.UI, "preparePredictions() $from", start)
}
@Synchronized
@Suppress("SameParameterValue", "UNUSED_PARAMETER")
private fun prepareBucketedData(from: String) {
// val start = dateUtil.now()
val bucketedData = iobCobCalculator.ads.getBucketedDataTableCopy() ?: return
if (bucketedData.isEmpty()) {
aapsLogger.debug("No bucketed data.")
return
}
val bucketedListArray: MutableList<DataPointWithLabelInterface> = ArrayList()
for (inMemoryGlucoseValue in bucketedData) {
if (inMemoryGlucoseValue.timestamp < overviewData.fromTime || inMemoryGlucoseValue.timestamp > overviewData.toTime) continue
bucketedListArray.add(InMemoryGlucoseValueDataPoint(inMemoryGlucoseValue, profileFunction, resourceHelper))
}
overviewData.bucketedGraphSeries = PointsWithLabelGraphSeries(Array(bucketedListArray.size) { i -> bucketedListArray[i] })
// profiler.log(LTag.UI, "prepareBucketedData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
private fun prepareBasalData(from: String) {
// val start = dateUtil.now()
overviewData.maxBasalValueFound = 0.0
val baseBasalArray: MutableList<ScaledDataPoint> = ArrayList()
val tempBasalArray: MutableList<ScaledDataPoint> = ArrayList()
val basalLineArray: MutableList<ScaledDataPoint> = ArrayList()
val absoluteBasalLineArray: MutableList<ScaledDataPoint> = ArrayList()
var lastLineBasal = 0.0
var lastAbsoluteLineBasal = -1.0
var lastBaseBasal = 0.0
var lastTempBasal = 0.0
var time = overviewData.fromTime
while (time < overviewData.toTime) {
val profile = profileFunction.getProfile(time)
if (profile == null) {
time += 60 * 1000L
continue
}
val basalData = iobCobCalculator.getBasalData(profile, time)
val baseBasalValue = basalData.basal
var absoluteLineValue = baseBasalValue
var tempBasalValue = 0.0
var basal = 0.0
if (basalData.isTempBasalRunning) {
tempBasalValue = basalData.tempBasalAbsolute
absoluteLineValue = tempBasalValue
if (tempBasalValue != lastTempBasal) {
tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, overviewData.basalScale))
tempBasalArray.add(ScaledDataPoint(time, tempBasalValue.also { basal = it }, overviewData.basalScale))
}
if (lastBaseBasal != 0.0) {
baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, overviewData.basalScale))
baseBasalArray.add(ScaledDataPoint(time, 0.0, overviewData.basalScale))
lastBaseBasal = 0.0
}
} else {
if (baseBasalValue != lastBaseBasal) {
baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, overviewData.basalScale))
baseBasalArray.add(ScaledDataPoint(time, baseBasalValue.also { basal = it }, overviewData.basalScale))
lastBaseBasal = baseBasalValue
}
if (lastTempBasal != 0.0) {
tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, overviewData.basalScale))
tempBasalArray.add(ScaledDataPoint(time, 0.0, overviewData.basalScale))
}
}
if (baseBasalValue != lastLineBasal) {
basalLineArray.add(ScaledDataPoint(time, lastLineBasal, overviewData.basalScale))
basalLineArray.add(ScaledDataPoint(time, baseBasalValue, overviewData.basalScale))
}
if (absoluteLineValue != lastAbsoluteLineBasal) {
absoluteBasalLineArray.add(ScaledDataPoint(time, lastAbsoluteLineBasal, overviewData.basalScale))
absoluteBasalLineArray.add(ScaledDataPoint(time, basal, overviewData.basalScale))
}
lastAbsoluteLineBasal = absoluteLineValue
lastLineBasal = baseBasalValue
lastTempBasal = tempBasalValue
overviewData.maxBasalValueFound = max(overviewData.maxBasalValueFound, max(tempBasalValue, baseBasalValue))
time += 60 * 1000L
}
// final points
basalLineArray.add(ScaledDataPoint(overviewData.toTime, lastLineBasal, overviewData.basalScale))
baseBasalArray.add(ScaledDataPoint(overviewData.toTime, lastBaseBasal, overviewData.basalScale))
tempBasalArray.add(ScaledDataPoint(overviewData.toTime, lastTempBasal, overviewData.basalScale))
absoluteBasalLineArray.add(ScaledDataPoint(overviewData.toTime, lastAbsoluteLineBasal, overviewData.basalScale))
// create series
overviewData.baseBasalGraphSeries = LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = resourceHelper.gc(R.color.basebasal)
it.thickness = 0
}
overviewData.tempBasalGraphSeries = LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = resourceHelper.gc(R.color.tempbasal)
it.thickness = 0
}
overviewData.basalLineGraphSeries = LineGraphSeries(Array(basalLineArray.size) { i -> basalLineArray[i] }).also {
it.setCustomPaint(Paint().also { paint ->
paint.style = Paint.Style.STROKE
paint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2
paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f)
paint.color = resourceHelper.gc(R.color.basal)
})
}
overviewData.absoluteBasalGraphSeries = LineGraphSeries(Array(absoluteBasalLineArray.size) { i -> absoluteBasalLineArray[i] }).also {
it.setCustomPaint(Paint().also { absolutePaint ->
absolutePaint.style = Paint.Style.STROKE
absolutePaint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2
absolutePaint.color = resourceHelper.gc(R.color.basal)
})
}
// profiler.log(LTag.UI, "prepareBasalData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
private fun prepareTemporaryTargetData(from: String) {
// val start = dateUtil.now()
val profile = overviewData.profile ?: return
val units = profileFunction.getUnits()
var toTime = overviewData.toTime
val targetsSeriesArray: MutableList<DataPoint> = ArrayList()
var lastTarget = -1.0
loopPlugin.lastRun?.constraintsProcessed?.let { toTime = max(it.latestPredictionsTime, toTime) }
var time = overviewData.fromTime
while (time < toTime) {
val tt = repository.getTemporaryTargetActiveAt(time).blockingGet()
val value: Double = if (tt is ValueWrapper.Existing) {
Profile.fromMgdlToUnits(tt.value.target(), units)
} else {
Profile.fromMgdlToUnits((profile.getTargetLowMgdl(time) + profile.getTargetHighMgdl(time)) / 2, units)
}
if (lastTarget != value) {
if (lastTarget != -1.0) targetsSeriesArray.add(DataPoint(time.toDouble(), lastTarget))
targetsSeriesArray.add(DataPoint(time.toDouble(), value))
}
lastTarget = value
time += 5 * 60 * 1000L
}
// final point
targetsSeriesArray.add(DataPoint(toTime.toDouble(), lastTarget))
// create series
overviewData.temporaryTargetSeries = LineGraphSeries(Array(targetsSeriesArray.size) { i -> targetsSeriesArray[i] }).also {
it.isDrawBackground = false
it.color = resourceHelper.gc(R.color.tempTargetBackground)
it.thickness = 2
}
// profiler.log(LTag.UI, "prepareTemporaryTargetData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
private fun prepareTreatmentsData(from: String) {
// val start = dateUtil.now()
overviewData.maxTreatmentsValue = 0.0
val filteredTreatments: MutableList<DataPointWithLabelInterface> = ArrayList()
repository.getBolusesIncludingInvalidFromTimeToTime(overviewData.fromTime, overviewData.endTime, true).blockingGet()
.map { BolusDataPoint(it, resourceHelper, activePlugin, defaultValueHelper) }
.filter { it.data.type != Bolus.Type.SMB || it.data.isValid }
.forEach {
it.y = getNearestBg(it.x.toLong())
filteredTreatments.add(it)
}
repository.getCarbsIncludingInvalidFromTimeToTimeExpanded(overviewData.fromTime, overviewData.endTime, true).blockingGet()
.map { CarbsDataPoint(it, resourceHelper) }
.forEach {
it.y = getNearestBg(it.x.toLong())
filteredTreatments.add(it)
}
// ProfileSwitch
repository.getEffectiveProfileSwitchDataFromTimeToTime(overviewData.fromTime, overviewData.endTime, true).blockingGet()
.map { EffectiveProfileSwitchDataPoint(it) }
.forEach(filteredTreatments::add)
// Extended bolus
if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) {
repository.getExtendedBolusDataFromTimeToTime(overviewData.fromTime, overviewData.endTime, true).blockingGet()
.map { ExtendedBolusDataPoint(it) }
.filter { it.duration != 0L }
.forEach {
it.y = getNearestBg(it.x.toLong())
filteredTreatments.add(it)
}
}
// Careportal
repository.compatGetTherapyEventDataFromToTime(overviewData.fromTime - T.hours(6).msecs(), overviewData.endTime).blockingGet()
.map { TherapyEventDataPoint(it, resourceHelper, profileFunction, translator) }
.filterTimeframe(overviewData.fromTime, overviewData.endTime)
.forEach {
if (it.y == 0.0) it.y = getNearestBg(it.x.toLong())
filteredTreatments.add(it)
}
// increase maxY if a treatment forces it's own height that's higher than a BG value
filteredTreatments.map { it.y }
.maxOrNull()
?.let(::addUpperChartMargin)
?.let { overviewData.maxTreatmentsValue = maxOf(overviewData.maxTreatmentsValue, it) }
overviewData.treatmentsSeries = PointsWithLabelGraphSeries(filteredTreatments.toTypedArray())
// profiler.log(LTag.UI, "prepareTreatmentsData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
private fun prepareIobAutosensData(from: String) {
// val start = dateUtil.now()
val iobArray: MutableList<ScaledDataPoint> = ArrayList()
val absIobArray: MutableList<ScaledDataPoint> = ArrayList()
overviewData.maxIobValueFound = Double.MIN_VALUE
var lastIob = 0.0
var absLastIob = 0.0
var time = overviewData.fromTime
val minFailOverActiveList: MutableList<DataPointWithLabelInterface> = ArrayList()
val cobArray: MutableList<ScaledDataPoint> = ArrayList()
overviewData.maxCobValueFound = Double.MIN_VALUE
var lastCob = 0
val actArrayHist: MutableList<ScaledDataPoint> = ArrayList()
val actArrayPrediction: MutableList<ScaledDataPoint> = ArrayList()
val now = dateUtil.now().toDouble()
overviewData.maxIAValue = 0.0
val bgiArrayHist: MutableList<ScaledDataPoint> = ArrayList()
val bgiArrayPrediction: MutableList<ScaledDataPoint> = ArrayList()
overviewData.maxBGIValue = Double.MIN_VALUE
val devArray: MutableList<DeviationDataPoint> = ArrayList()
overviewData.maxDevValueFound = Double.MIN_VALUE
val ratioArray: MutableList<ScaledDataPoint> = ArrayList()
overviewData.maxRatioValueFound = 5.0 //even if sens data equals 0 for all the period, minimum scale is between 95% and 105%
overviewData.minRatioValueFound = -5.0
val dsMaxArray: MutableList<ScaledDataPoint> = ArrayList()
val dsMinArray: MutableList<ScaledDataPoint> = ArrayList()
overviewData.maxFromMaxValueFound = Double.MIN_VALUE
overviewData.maxFromMinValueFound = Double.MIN_VALUE
val adsData = iobCobCalculator.ads.clone()
while (time <= overviewData.toTime) {
val profile = profileFunction.getProfile(time)
if (profile == null) {
time += 5 * 60 * 1000L
continue
}
// IOB
val iob = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile)
val baseBasalIob = iobCobCalculator.calculateAbsoluteIobFromBaseBasals(time)
val absIob = IobTotal.combine(iob, baseBasalIob)
val autosensData = adsData.getAutosensDataAtTime(time)
if (abs(lastIob - iob.iob) > 0.02) {
if (abs(lastIob - iob.iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, overviewData.iobScale))
iobArray.add(ScaledDataPoint(time, iob.iob, overviewData.iobScale))
overviewData.maxIobValueFound = maxOf(overviewData.maxIobValueFound, abs(iob.iob))
lastIob = iob.iob
}
if (abs(absLastIob - absIob.iob) > 0.02) {
if (abs(absLastIob - absIob.iob) > 0.2) absIobArray.add(ScaledDataPoint(time, absLastIob, overviewData.iobScale))
absIobArray.add(ScaledDataPoint(time, absIob.iob, overviewData.iobScale))
overviewData.maxIobValueFound = maxOf(overviewData.maxIobValueFound, abs(absIob.iob))
absLastIob = absIob.iob
}
// COB
if (autosensData != null) {
val cob = autosensData.cob.toInt()
if (cob != lastCob) {
if (autosensData.carbsFromBolus > 0) cobArray.add(ScaledDataPoint(time, lastCob.toDouble(), overviewData.cobScale))
cobArray.add(ScaledDataPoint(time, cob.toDouble(), overviewData.cobScale))
overviewData.maxCobValueFound = max(overviewData.maxCobValueFound, cob.toDouble())
lastCob = cob
}
if (autosensData.failoverToMinAbsorbtionRate) {
autosensData.setScale(overviewData.cobScale)
autosensData.setChartTime(time)
minFailOverActiveList.add(autosensData)
}
}
// ACTIVITY
if (time <= now) actArrayHist.add(ScaledDataPoint(time, iob.activity, overviewData.actScale))
else actArrayPrediction.add(ScaledDataPoint(time, iob.activity, overviewData.actScale))
overviewData.maxIAValue = max(overviewData.maxIAValue, abs(iob.activity))
// BGI
val devBgiScale = overviewMenus.isEnabledIn(OverviewMenus.CharType.DEV) == overviewMenus.isEnabledIn(OverviewMenus.CharType.BGI)
val deviation = if (devBgiScale) autosensData?.deviation ?: 0.0 else 0.0
val bgi: Double = iob.activity * profile.getIsfMgdl(time) * 5.0
if (time <= now) bgiArrayHist.add(ScaledDataPoint(time, bgi, overviewData.bgiScale))
else bgiArrayPrediction.add(ScaledDataPoint(time, bgi, overviewData.bgiScale))
overviewData.maxBGIValue = max(overviewData.maxBGIValue, max(abs(bgi), deviation))
// DEVIATIONS
if (autosensData != null) {
var color = resourceHelper.gc(R.color.deviationblack) // "="
if (autosensData.type == "" || autosensData.type == "non-meal") {
if (autosensData.pastSensitivity == "C") color = resourceHelper.gc(R.color.deviationgrey)
if (autosensData.pastSensitivity == "+") color = resourceHelper.gc(R.color.deviationgreen)
if (autosensData.pastSensitivity == "-") color = resourceHelper.gc(R.color.deviationred)
} else if (autosensData.type == "uam") {
color = resourceHelper.gc(R.color.uam)
} else if (autosensData.type == "csf") {
color = resourceHelper.gc(R.color.deviationgrey)
}
devArray.add(DeviationDataPoint(time.toDouble(), autosensData.deviation, color, overviewData.devScale))
overviewData.maxDevValueFound = maxOf(overviewData.maxDevValueFound, abs(autosensData.deviation), abs(bgi))
}
// RATIO
if (autosensData != null) {
ratioArray.add(ScaledDataPoint(time, 100.0 * (autosensData.autosensResult.ratio - 1), overviewData.ratioScale))
overviewData.maxRatioValueFound = max(overviewData.maxRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1))
overviewData.minRatioValueFound = min(overviewData.minRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1))
}
// DEV SLOPE
if (autosensData != null) {
dsMaxArray.add(ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, overviewData.dsMaxScale))
dsMinArray.add(ScaledDataPoint(time, autosensData.slopeFromMinDeviation, overviewData.dsMinScale))
overviewData.maxFromMaxValueFound = max(overviewData.maxFromMaxValueFound, abs(autosensData.slopeFromMaxDeviation))
overviewData.maxFromMinValueFound = max(overviewData.maxFromMinValueFound, abs(autosensData.slopeFromMinDeviation))
}
time += 5 * 60 * 1000L
}
// IOB
overviewData.iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50%
it.color = resourceHelper.gc(R.color.iob)
it.thickness = 3
}
overviewData.absIobSeries = FixedLineGraphSeries(Array(absIobArray.size) { i -> absIobArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50%
it.color = resourceHelper.gc(R.color.iob)
it.thickness = 3
}
if (overviewMenus.setting[0][OverviewMenus.CharType.PRE.ordinal]) {
val autosensData = adsData.getLastAutosensData("GraphData", aapsLogger, dateUtil)
val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult()
val isTempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing
val iobPrediction: MutableList<DataPointWithLabelInterface> = ArrayList()
val iobPredictionArray = iobCobCalculator.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
for (i in iobPredictionArray) {
iobPrediction.add(i.setColor(resourceHelper.gc(R.color.iobPredAS)))
overviewData.maxIobValueFound = max(overviewData.maxIobValueFound, abs(i.iob))
}
overviewData.iobPredictions1Series = PointsWithLabelGraphSeries(Array(iobPrediction.size) { i -> iobPrediction[i] })
val iobPrediction2: MutableList<DataPointWithLabelInterface> = ArrayList()
val iobPredictionArray2 = iobCobCalculator.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
for (i in iobPredictionArray2) {
iobPrediction2.add(i.setColor(resourceHelper.gc(R.color.iobPred)))
overviewData.maxIobValueFound = max(overviewData.maxIobValueFound, abs(i.iob))
}
overviewData.iobPredictions2Series = PointsWithLabelGraphSeries(Array(iobPrediction2.size) { i -> iobPrediction2[i] })
aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray))
aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray2))
} else {
overviewData.iobPredictions1Series = PointsWithLabelGraphSeries()
overviewData.iobPredictions2Series = PointsWithLabelGraphSeries()
}
// COB
overviewData.cobSeries = FixedLineGraphSeries(Array(cobArray.size) { i -> cobArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.cob) //50%
it.color = resourceHelper.gc(R.color.cob)
it.thickness = 3
}
overviewData.cobMinFailOverSeries = PointsWithLabelGraphSeries(Array(minFailOverActiveList.size) { i -> minFailOverActiveList[i] })
// ACTIVITY
overviewData.activitySeries = FixedLineGraphSeries(Array(actArrayHist.size) { i -> actArrayHist[i] }).also {
it.isDrawBackground = false
it.color = resourceHelper.gc(R.color.activity)
it.thickness = 3
}
overviewData.activityPredictionSeries = FixedLineGraphSeries(Array(actArrayPrediction.size) { i -> actArrayPrediction[i] }).also {
it.setCustomPaint(Paint().also { paint ->
paint.style = Paint.Style.STROKE
paint.strokeWidth = 3f
paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f)
paint.color = resourceHelper.gc(R.color.activity)
})
}
// BGI
overviewData.minusBgiSeries = FixedLineGraphSeries(Array(bgiArrayHist.size) { i -> bgiArrayHist[i] }).also {
it.isDrawBackground = false
it.color = resourceHelper.gc(R.color.bgi)
it.thickness = 3
}
overviewData.minusBgiHistSeries = FixedLineGraphSeries(Array(bgiArrayPrediction.size) { i -> bgiArrayPrediction[i] }).also {
it.setCustomPaint(Paint().also { paint ->
paint.style = Paint.Style.STROKE
paint.strokeWidth = 3f
paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f)
paint.color = resourceHelper.gc(R.color.bgi)
})
}
// DEVIATIONS
overviewData.deviationsSeries = BarGraphSeries(Array(devArray.size) { i -> devArray[i] }).also {
it.setValueDependentColor { data: DeviationDataPoint -> data.color }
}
// RATIO
overviewData.ratioSeries = LineGraphSeries(Array(ratioArray.size) { i -> ratioArray[i] }).also {
it.color = resourceHelper.gc(R.color.ratio)
it.thickness = 3
}
// DEV SLOPE
overviewData.dsMaxSeries = LineGraphSeries(Array(dsMaxArray.size) { i -> dsMaxArray[i] }).also {
it.color = resourceHelper.gc(R.color.devslopepos)
it.thickness = 3
}
overviewData.dsMinSeries = LineGraphSeries(Array(dsMinArray.size) { i -> dsMinArray[i] }).also {
it.color = resourceHelper.gc(R.color.devslopeneg)
it.thickness = 3
}
// profiler.log(LTag.UI, "prepareIobAutosensData() $from", start)
}
private fun addUpperChartMargin(maxBgValue: Double) =
if (profileFunction.getUnits() == GlucoseUnit.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4
private fun getNearestBg(date: Long): Double {
overviewData.bgReadingsArray.let { bgReadingsArray ->
for (reading in bgReadingsArray) {
if (reading.timestamp > date) continue
return Profile.fromMgdlToUnits(reading.value, profileFunction.getUnits())
}
return if (bgReadingsArray.isNotEmpty()) Profile.fromMgdlToUnits(bgReadingsArray[0].value, profileFunction.getUnits())
else Profile.fromMgdlToUnits(100.0, profileFunction.getUnits())
}
}
private fun <E : DataPointWithLabelInterface> List<E>.filterTimeframe(fromTime: Long, endTime: Long): List<E> =
filter { it.x + it.duration >= fromTime && it.x <= endTime }
}

View file

@ -26,16 +26,16 @@ import kotlin.math.max
class GraphData(
injector: HasAndroidInjector,
private val graph: GraphView
private val graph: GraphView,
private val overviewData: OverviewData
) {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var defaultValueHelper: DefaultValueHelper
@Inject lateinit var overviewData: OverviewData
var maxY = Double.MIN_VALUE
private var maxY = Double.MIN_VALUE
private var minY = Double.MAX_VALUE
private val units: GlucoseUnit
private val series: MutableList<Series<*>> = ArrayList()
@ -201,7 +201,7 @@ class GraphData(
graph.gridLabelRenderer.numHorizontalLabels = 7 // only 7 because of the space
}
internal fun addSeries(s: Series<*>) = series.add(s)
private fun addSeries(s: Series<*>) = series.add(s)
fun performUpdate() {
// clear old data

View file

@ -99,7 +99,7 @@ class IobCobOref1Thread internal constructor(
// 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))
rxBus.send(EventIobCalculationProgress(progress, cause))
if (iobCobCalculatorPlugin.stopCalculationTrigger) {
iobCobCalculatorPlugin.stopCalculationTrigger = false
aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from")
@ -325,7 +325,7 @@ class IobCobOref1Thread internal constructor(
}.start()
} finally {
mWakeLock?.release()
rxBus.send(EventIobCalculationProgress(""))
rxBus.send(EventIobCalculationProgress("", cause))
aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from")
profiler.log(LTag.AUTOSENS, "IobCobOref1Thread", start)
}

View file

@ -98,7 +98,7 @@ class IobCobThread @Inject internal constructor(
// 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))
rxBus.send(EventIobCalculationProgress(progress, cause))
if (iobCobCalculatorPlugin.stopCalculationTrigger) {
iobCobCalculatorPlugin.stopCalculationTrigger = false
aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from")
@ -272,7 +272,7 @@ class IobCobThread @Inject internal constructor(
}.start()
} finally {
mWakeLock?.release()
rxBus.send(EventIobCalculationProgress(""))
rxBus.send(EventIobCalculationProgress("", cause))
aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from")
profiler.log(LTag.AUTOSENS, "IobCobThread", start)
}

View file

@ -2,4 +2,4 @@ package info.nightscout.androidaps.plugins.iob.iobCobCalculator.events
import info.nightscout.androidaps.events.Event
class EventIobCalculationProgress(var progress: String) : Event()
class EventIobCalculationProgress(val progress: String, val cause: Event?) : Event()

View file

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity">
tools:context="info.nightscout.androidaps.activities.HistoryBrowseActivity">
<LinearLayout
android:layout_width="match_parent"
@ -72,7 +72,7 @@
android:layout_weight="1">
<com.jjoe64.graphview.GraphView
android:id="@+id/bggraph"
android:id="@+id/bg_graph"
android:layout_width="wrap_content"
android:layout_height="match_parent" />

View file

@ -28,8 +28,6 @@ import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin
import info.nightscout.androidaps.plugins.source.GlimpPlugin
import info.nightscout.androidaps.plugins.treatments.TreatmentService
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.Profiler
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
@ -51,7 +49,7 @@ import java.util.*
@RunWith(PowerMockRunner::class)
@PrepareForTest(
ConstraintChecker::class, SP::class, Context::class,
OpenAPSAMAPlugin::class, OpenAPSSMBPlugin::class, TreatmentsPlugin::class, TreatmentService::class,
OpenAPSAMAPlugin::class, OpenAPSSMBPlugin::class,
VirtualPumpPlugin::class, DetailedBolusInfoStorage::class, TemporaryBasalStorage::class, GlimpPlugin::class, Profiler::class,
UserEntryLogger::class, LoggerUtils::class, AppRepository::class, InsightDatabaseDao::class)
class ConstraintsCheckerTest : TestBaseWithProfile() {

View file

@ -14,7 +14,6 @@ import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.configBuilder.RunningConfiguration
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import info.nightscout.androidaps.receivers.ReceiverStatusStore
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
@ -41,7 +40,6 @@ class LoopPluginTest : TestBase() {
@Mock lateinit var context: Context
@Mock lateinit var commandQueue: CommandQueueProvider
@Mock lateinit var activePlugin: ActivePlugin
@Mock lateinit var treatmentsPlugin: TreatmentsPlugin
@Mock lateinit var virtualPumpPlugin: VirtualPumpPlugin
@Mock lateinit var iobCobCalculator: IobCobCalculator
@Mock lateinit var fabricPrivacy: FabricPrivacy

View file

@ -20,7 +20,6 @@ import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import info.nightscout.androidaps.queue.commands.*
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
@ -45,7 +44,7 @@ import java.util.*
@RunWith(PowerMockRunner::class)
@PrepareForTest(
ConstraintChecker::class, VirtualPumpPlugin::class, ToastUtils::class, Context::class,
TreatmentsPlugin::class, FabricPrivacy::class, LoggerUtils::class, PowerManager::class,
FabricPrivacy::class, LoggerUtils::class, PowerManager::class,
AppRepository::class)
class CommandQueueTest : TestBaseWithProfile() {

View file

@ -14,7 +14,6 @@ import info.nightscout.androidaps.interfaces.PumpSync
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import info.nightscout.androidaps.queue.commands.Command
import info.nightscout.androidaps.queue.commands.CommandTempBasalAbsolute
import info.nightscout.androidaps.utils.FabricPrivacy
@ -34,7 +33,7 @@ import org.powermock.modules.junit4.PowerMockRunner
@RunWith(PowerMockRunner::class)
@PrepareForTest(
ConstraintChecker::class, VirtualPumpPlugin::class, ToastUtils::class, Context::class,
TreatmentsPlugin::class, FabricPrivacy::class, LoggerUtils::class, PowerManager::class)
FabricPrivacy::class, LoggerUtils::class, PowerManager::class)
class QueueThreadTest : TestBaseWithProfile() {
@Mock lateinit var constraintChecker: ConstraintChecker

View file

@ -13,7 +13,6 @@ import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.resources.ResourceHelper
import org.junit.Assert
@ -40,7 +39,6 @@ class BolusWizardTest : TestBase() {
@Mock lateinit var commandQueue: CommandQueueProvider
@Mock lateinit var loopPlugin: LoopPlugin
@Mock lateinit var iobCobCalculator: IobCobCalculator
@Mock lateinit var treatmentsPlugin: TreatmentsPlugin
@Mock lateinit var virtualPumpPlugin: VirtualPumpPlugin
@Mock lateinit var dateUtil: DateUtil
@Mock lateinit var autosensDataStore: AutosensDataStore
@ -71,7 +69,6 @@ class BolusWizardTest : TestBase() {
`when`(profile.getIc()).thenReturn(insulinToCarbRatio)
`when`(profileFunction.getUnits()).thenReturn(GlucoseUnit.MGDL)
`when`(activePlugin.activeTreatments).thenReturn(treatmentsPlugin)
`when`(iobCobCalculator.calculateIobFromBolus()).thenReturn(IobTotal(System.currentTimeMillis()))
`when`(iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended()).thenReturn(IobTotal(System.currentTimeMillis()))
`when`(activePlugin.activePump).thenReturn(virtualPumpPlugin)