Merge pull request #31 from 0pen-dash/avereha/merge-dev

avereha/merge dev
This commit is contained in:
Andrei Vereha 2021-06-03 22:01:30 +02:00 committed by GitHub
commit 311d12ffe6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
620 changed files with 25090 additions and 22456 deletions

73
.circleci/config.yml Normal file
View file

@ -0,0 +1,73 @@
# Use the latest 2.1 version of CircleCI pipeline process engine.
# See: https://circleci.com/docs/2.0/configuration-reference
version: 2.1
# Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects.
orbs:
android: circleci/android@1.0.3
codecov: codecov/codecov@1.2.0
jobs:
# Below is the definition of your job to build and test your app, you can rename and customize it as you want.
build-and-test:
# These next lines define the Android machine image executor: https://circleci.com/docs/2.0/executor-types/
executor:
name: android/android-machine
steps:
# Checkout the code as the first step.
- checkout
# The next step will run the unit tests
- android/run-tests:
test-command: ./gradlew -Pcoverage -PfirebaseDisable testFullDebugUnitTest jacocoTestFullDebugUnitTestReport
# Then start the emulator and run the Instrumentation tests!
# - android/start-emulator-and-run-tests:
# test-command: ./gradlew connectedDebugAndroidTest
# system-image: system-images;android-25;google_apis;x86
# And finally run the release build
# - run:
# name: Assemble release build
# command: |
# ./gradlew assembleRelease
- codecov/upload:
file: './app/build/jacoco/jacoco.xml'
- codecov/upload:
file: './automation/build/jacoco/jacoco.xml'
- codecov/upload:
file: './combo/build/jacoco/jacoco.xml'
- codecov/upload:
file: './core/build/jacoco/jacoco.xml'
- codecov/upload:
file: './dana/build/jacoco/jacoco.xml'
- codecov/upload:
file: './danar/build/jacoco/jacoco.xml'
- codecov/upload:
file: './danars/build/jacoco/jacoco.xml'
- codecov/upload:
file: './database/build/jacoco/jacoco.xml'
- codecov/upload:
file: './insight/build/jacoco/jacoco.xml'
- codecov/upload:
file: './medtronic/build/jacoco/jacoco.xml'
- codecov/upload:
file: './omnipod-common/build/jacoco/jacoco.xml'
- codecov/upload:
file: './omnipod-dash/build/jacoco/jacoco.xml'
- codecov/upload:
file: './omnipod-eros/build/jacoco/jacoco.xml'
- codecov/upload:
file: './rileylink/build/jacoco/jacoco.xml'
- codecov/upload:
file: './wear/build/jacoco/jacoco.xml'
workflows:
# Below is the definition of your workflow.
# Inside the workflow, you provide the jobs you want to run, e.g this workflow runs the build-and-test job above.
# CircleCI will run this workflow on every commit.
# For more details on extending your workflow, see the configuration docs: https://circleci.com/docs/2.0/configuration-reference/#workflows
sample:
jobs:
- build-and-test

View file

@ -111,7 +111,7 @@ android {
defaultConfig { defaultConfig {
multiDexEnabled true multiDexEnabled true
versionCode 1500 versionCode 1500
version "2.8.2.1-dev-e3" version "2.8.2.1-dev-e5"
buildConfigField "String", "VERSION", '"' + version + '"' buildConfigField "String", "VERSION", '"' + version + '"'
buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"' buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"'
buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"' buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"'
@ -186,6 +186,7 @@ dependencies {
implementation project(':danars') implementation project(':danars')
implementation project(':danar') implementation project(':danar')
implementation project(':insight') implementation project(':insight')
implementation project(':pump-common')
implementation project(':rileylink') implementation project(':rileylink')
implementation project(':medtronic') implementation project(':medtronic')
implementation project(':omnipod-common') implementation project(':omnipod-common')
@ -197,8 +198,6 @@ dependencies {
/* Dagger2 - We are going to use dagger.android which includes /* Dagger2 - We are going to use dagger.android which includes
* support for Activity and fragment injection so we need to include * support for Activity and fragment injection so we need to include
* the following dependencies */ * the following dependencies */
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
annotationProcessor "com.google.dagger:dagger-android-processor:$dagger_version" annotationProcessor "com.google.dagger:dagger-android-processor:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version" kapt "com.google.dagger:dagger-android-processor:$dagger_version"

View file

@ -68,7 +68,8 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".plugins.general.maintenance.activities.PrefImportListActivity" /> <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.SurveyActivity" />
<activity android:name=".activities.ProfileHelperActivity" <activity android:name=".activities.ProfileHelperActivity"
android:theme="@style/ProfileHelperAppTheme" /> android:theme="@style/ProfileHelperAppTheme" />

View file

@ -27,18 +27,14 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.joanzapata.iconify.Iconify import com.joanzapata.iconify.Iconify
import com.joanzapata.iconify.fonts.FontAwesomeModule import com.joanzapata.iconify.fonts.FontAwesomeModule
import dev.doubledot.doki.ui.DokiActivity import dev.doubledot.doki.ui.DokiActivity
import info.nightscout.androidaps.activities.NoSplashAppCompatActivity import info.nightscout.androidaps.activities.*
import info.nightscout.androidaps.activities.PreferencesActivity
import info.nightscout.androidaps.activities.ProfileHelperActivity
import info.nightscout.androidaps.activities.SingleFragmentActivity
import info.nightscout.androidaps.activities.StatsActivity
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.databinding.ActivityMainBinding import info.nightscout.androidaps.databinding.ActivityMainBinding
import info.nightscout.androidaps.events.EventAppExit import info.nightscout.androidaps.events.EventAppExit
import info.nightscout.androidaps.events.EventPreferenceChange import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.events.EventRebuildTabs 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.ActivePlugin
import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.IconsProvider import info.nightscout.androidaps.interfaces.IconsProvider
@ -99,6 +95,7 @@ class MainActivity : NoSplashAppCompatActivity() {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
@kotlin.ExperimentalStdlibApi
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Iconify.with(FontAwesomeModule()) Iconify.with(FontAwesomeModule())
@ -290,6 +287,11 @@ class MainActivity : NoSplashAppCompatActivity() {
return true return true
} }
R.id.nav_treatments -> {
startActivity(Intent(this, TreatmentsActivity::class.java))
return true
}
R.id.nav_setupwizard -> { R.id.nav_setupwizard -> {
protectionCheck.queryProtection(this, ProtectionCheck.Protection.PREFERENCES, { protectionCheck.queryProtection(this, ProtectionCheck.Protection.PREFERENCES, {
startActivity(Intent(this, SetupWizardActivity::class.java)) startActivity(Intent(this, SetupWizardActivity::class.java))
@ -359,11 +361,12 @@ class MainActivity : NoSplashAppCompatActivity() {
// Correct place for calling setUserStats() would be probably MainApp // Correct place for calling setUserStats() would be probably MainApp
// but we need to have it called at least once a day. Thus this location // but we need to have it called at least once a day. Thus this location
@kotlin.ExperimentalStdlibApi
private fun setUserStats() { private fun setUserStats() {
if (!fabricPrivacy.fabricEnabled()) return if (!fabricPrivacy.fabricEnabled()) return
val closedLoopEnabled = if (constraintChecker.isClosedLoopAllowed().value()) "CLOSED_LOOP_ENABLED" else "CLOSED_LOOP_DISABLED" val closedLoopEnabled = if (constraintChecker.isClosedLoopAllowed().value()) "CLOSED_LOOP_ENABLED" else "CLOSED_LOOP_DISABLED"
// Size is limited to 36 chars // Size is limited to 36 chars
val remote = BuildConfig.REMOTE.toLowerCase(Locale.getDefault()) val remote = BuildConfig.REMOTE.lowercase(Locale.getDefault())
.replace("https://", "") .replace("https://", "")
.replace("http://", "") .replace("http://", "")
.replace(".git", "") .replace(".git", "")

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

@ -7,8 +7,10 @@ import javax.inject.Inject
class RequestDexcomPermissionActivity : DialogAppCompatActivity() { class RequestDexcomPermissionActivity : DialogAppCompatActivity() {
@Inject lateinit var dexcomPlugin: DexcomPlugin @Inject lateinit var dexcomPlugin: DexcomPlugin
private val requestCode = "AndroidAPS <3".map { it.toInt() }.sum() @kotlin.ExperimentalStdlibApi
private val requestCode = "AndroidAPS <3".map { it.code }.sum()
@kotlin.ExperimentalStdlibApi
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
requestPermissions(arrayOf(DexcomPlugin.PERMISSION), requestCode) requestPermissions(arrayOf(DexcomPlugin.PERMISSION), requestCode)

View file

@ -1,53 +1,28 @@
package info.nightscout.androidaps.plugins.treatments package info.nightscout.androidaps.activities
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction import androidx.fragment.app.FragmentTransaction
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.fragments.*
import info.nightscout.androidaps.databinding.TreatmentsFragmentBinding import info.nightscout.androidaps.databinding.TreatmentsFragmentBinding
import info.nightscout.androidaps.events.EventExtendedBolusChange
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.treatments.fragments.*
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import javax.inject.Inject import javax.inject.Inject
class TreatmentsFragment : DaggerFragment() { class TreatmentsActivity : NoSplashAppCompatActivity() {
@Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var iobCobCalculator: IobCobCalculator
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var buildHelper: BuildHelper @Inject lateinit var buildHelper: BuildHelper
@Inject lateinit var dateUtil: DateUtil @Inject lateinit var activePlugin: ActivePlugin
private val disposable = CompositeDisposable() private lateinit var binding: TreatmentsFragmentBinding
private var _binding: TreatmentsFragmentBinding? = null override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// This property is only valid between onCreateView and binding = TreatmentsFragmentBinding.inflate(layoutInflater)
// onDestroyView. setContentView(binding.root)
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
TreatmentsFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.tempBasals.visibility = buildHelper.isEngineeringMode().toVisibility() binding.tempBasals.visibility = buildHelper.isEngineeringMode().toVisibility()
binding.extendedBoluses.visibility = (buildHelper.isEngineeringMode() && !activePlugin.activePump.isFakingTempsByExtendedBoluses).toVisibility() binding.extendedBoluses.visibility = (buildHelper.isEngineeringMode() && !activePlugin.activePump.isFakingTempsByExtendedBoluses).toVisibility()
@ -84,33 +59,10 @@ class TreatmentsFragment : DaggerFragment() {
setBackgroundColorOnSelected(binding.treatments) setBackgroundColorOnSelected(binding.treatments)
} }
@Synchronized
override fun onResume() {
super.onResume()
disposable += rxBus
.toObservable(EventExtendedBolusChange::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ updateGui() }, fabricPrivacy::logException)
updateGui()
}
@Synchronized
override fun onPause() {
super.onPause()
disposable.clear()
}
@Synchronized
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun setFragment(selectedFragment: Fragment) { private fun setFragment(selectedFragment: Fragment) {
childFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, selectedFragment) // f2_container is your FrameLayout container .replace(R.id.fragment_container, selectedFragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.addToBackStack(null)
.commit() .commit()
} }
@ -125,8 +77,4 @@ class TreatmentsFragment : DaggerFragment() {
selected.setBackgroundColor(resourceHelper.gc(R.color.tabBgColorSelected)) selected.setBackgroundColor(resourceHelper.gc(R.color.tabBgColorSelected))
} }
private fun updateGui() {
if (_binding == null) return
binding.extendedBoluses.visibility = (activePlugin.activePump.pumpDescription.isExtendedBolusCapable || iobCobCalculator.getExtendedBolus(dateUtil.now()) != null).toVisibility()
}
} }

View file

@ -1,4 +1,4 @@
package info.nightscout.androidaps.plugins.treatments.fragments package info.nightscout.androidaps.activities.fragments
import android.graphics.Paint import android.graphics.Paint
import android.os.Bundle import android.os.Bundle
@ -156,7 +156,7 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
}) })
} }
} }
val nsUploadOnly = sp.getBoolean(R.string.key_ns_upload_only, true) || !buildHelper.isEngineeringMode() val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_insulin, false) || !sp.getBoolean(R.string.key_ns_receive_carbs, false) || !buildHelper.isEngineeringMode()
if (nsUploadOnly) binding.refreshFromNightscout.visibility = View.GONE if (nsUploadOnly) binding.refreshFromNightscout.visibility = View.GONE
binding.showInvalidated.setOnCheckedChangeListener { _, _ -> binding.showInvalidated.setOnCheckedChangeListener { _, _ ->
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())

View file

@ -1,4 +1,4 @@
package info.nightscout.androidaps.plugins.treatments.fragments package info.nightscout.androidaps.activities.fragments
import android.graphics.Paint import android.graphics.Paint
import android.os.Bundle import android.os.Bundle
@ -25,7 +25,7 @@ import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.plugins.treatments.events.EventTreatmentUpdateGui import info.nightscout.androidaps.plugins.treatments.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsCareportalFragment.RecyclerViewAdapter.TherapyEventsViewHolder import info.nightscout.androidaps.activities.fragments.TreatmentsCareportalFragment.RecyclerViewAdapter.TherapyEventsViewHolder
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.T
@ -102,7 +102,7 @@ class TreatmentsCareportalFragment : DaggerFragment() {
} }
} }
val nsUploadOnly = sp.getBoolean(R.string.key_ns_upload_only, true) || !buildHelper.isEngineeringMode() val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_therapy_events, false) || !buildHelper.isEngineeringMode()
if (nsUploadOnly) binding.refreshFromNightscout.visibility = View.GONE if (nsUploadOnly) binding.refreshFromNightscout.visibility = View.GONE
binding.showInvalidated.setOnCheckedChangeListener { _, _ -> binding.showInvalidated.setOnCheckedChangeListener { _, _ ->
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())

View file

@ -1,4 +1,4 @@
package info.nightscout.androidaps.plugins.treatments.fragments package info.nightscout.androidaps.activities.fragments
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.DialogInterface import android.content.DialogInterface
@ -15,6 +15,7 @@ import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.ExtendedBolus import info.nightscout.androidaps.database.entities.ExtendedBolus
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.interfaces.end import info.nightscout.androidaps.database.interfaces.end
import info.nightscout.androidaps.database.transactions.InvalidateExtendedBolusTransaction import info.nightscout.androidaps.database.transactions.InvalidateExtendedBolusTransaction
import info.nightscout.androidaps.databinding.TreatmentsExtendedbolusFragmentBinding import info.nightscout.androidaps.databinding.TreatmentsExtendedbolusFragmentBinding
@ -29,7 +30,7 @@ import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsExtendedBolusesFragment.RecyclerViewAdapter.ExtendedBolusesViewHolder import info.nightscout.androidaps.activities.fragments.TreatmentsExtendedBolusesFragment.RecyclerViewAdapter.ExtendedBolusesViewHolder
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.T
@ -157,7 +158,11 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() {
${resourceHelper.gs(R.string.extended_bolus)} ${resourceHelper.gs(R.string.extended_bolus)}
${resourceHelper.gs(R.string.date)}: ${dateUtil.dateAndTimeString(extendedBolus.timestamp)} ${resourceHelper.gs(R.string.date)}: ${dateUtil.dateAndTimeString(extendedBolus.timestamp)}
""".trimIndent(), { _: DialogInterface, _: Int -> """.trimIndent(), { _: DialogInterface, _: Int ->
uel.log(Action.EXTENDED_BOLUS_REMOVED, Sources.Treatments) uel.log(Action.EXTENDED_BOLUS_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(extendedBolus.timestamp),
ValueWithUnit.Insulin(extendedBolus.amount),
ValueWithUnit.UnitPerHour(extendedBolus.rate),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(extendedBolus.duration).toInt()))
disposable += repository.runTransactionForResult(InvalidateExtendedBolusTransaction(extendedBolus.id)) disposable += repository.runTransactionForResult(InvalidateExtendedBolusTransaction(extendedBolus.id))
.subscribe( .subscribe(
{ aapsLogger.debug(LTag.DATABASE, "Removed extended bolus $extendedBolus") }, { aapsLogger.debug(LTag.DATABASE, "Removed extended bolus $extendedBolus") },

View file

@ -1,4 +1,4 @@
package info.nightscout.androidaps.plugins.treatments.fragments package info.nightscout.androidaps.activities.fragments
import android.graphics.Paint import android.graphics.Paint
import android.os.Bundle import android.os.Bundle
@ -21,18 +21,16 @@ import info.nightscout.androidaps.dialogs.ProfileViewerDialog
import info.nightscout.androidaps.events.EventProfileSwitchChanged import info.nightscout.androidaps.events.EventProfileSwitchChanged
import info.nightscout.androidaps.extensions.getCustomizedName import info.nightscout.androidaps.extensions.getCustomizedName
import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.UploadQueueInterface
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged
import info.nightscout.androidaps.plugins.treatments.events.EventTreatmentUpdateGui import info.nightscout.androidaps.plugins.treatments.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsProfileSwitchFragment.RecyclerProfileViewAdapter.ProfileSwitchViewHolder import info.nightscout.androidaps.activities.fragments.TreatmentsProfileSwitchFragment.RecyclerProfileViewAdapter.ProfileSwitchViewHolder
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.T
@ -55,8 +53,6 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() {
@Inject lateinit var localProfilePlugin: LocalProfilePlugin @Inject lateinit var localProfilePlugin: LocalProfilePlugin
@Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var nsUpload: NSUpload
@Inject lateinit var uploadQueue: UploadQueueInterface
@Inject lateinit var dateUtil: DateUtil @Inject lateinit var dateUtil: DateUtil
@Inject lateinit var buildHelper: BuildHelper @Inject lateinit var buildHelper: BuildHelper
@Inject lateinit var aapsSchedulers: AapsSchedulers @Inject lateinit var aapsSchedulers: AapsSchedulers
@ -103,7 +99,7 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() {
} }
} }
} }
if (sp.getBoolean(R.string.key_ns_upload_only, true) || !buildHelper.isEngineeringMode()) binding.refreshFromNightscout.visibility = View.GONE if (!sp.getBoolean(R.string.key_ns_receive_profile_switch, false) || !buildHelper.isEngineeringMode()) binding.refreshFromNightscout.visibility = View.GONE
binding.showInvalidated.setOnCheckedChangeListener { _, _ -> binding.showInvalidated.setOnCheckedChangeListener { _, _ ->
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())
} }

View file

@ -1,4 +1,4 @@
package info.nightscout.androidaps.plugins.treatments.fragments package info.nightscout.androidaps.activities.fragments
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.DialogInterface import android.content.DialogInterface
@ -23,14 +23,13 @@ import info.nightscout.androidaps.databinding.TreatmentsTemptargetFragmentBindin
import info.nightscout.androidaps.databinding.TreatmentsTemptargetItemBinding import info.nightscout.androidaps.databinding.TreatmentsTemptargetItemBinding
import info.nightscout.androidaps.events.EventTempTargetChange import info.nightscout.androidaps.events.EventTempTargetChange
import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.interfaces.UploadQueueInterface
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.plugins.treatments.events.EventTreatmentUpdateGui import info.nightscout.androidaps.plugins.treatments.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsTempTargetFragment.RecyclerViewAdapter.TempTargetsViewHolder import info.nightscout.androidaps.activities.fragments.TreatmentsTempTargetFragment.RecyclerViewAdapter.TempTargetsViewHolder
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.T
@ -58,7 +57,6 @@ class TreatmentsTempTargetFragment : DaggerFragment() {
@Inject lateinit var aapsLogger: AAPSLogger @Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var profileFunction: ProfileFunction @Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var uploadQueue: UploadQueueInterface
@Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var translator: Translator @Inject lateinit var translator: Translator
@Inject lateinit var dateUtil: DateUtil @Inject lateinit var dateUtil: DateUtil
@ -99,7 +97,7 @@ class TreatmentsTempTargetFragment : DaggerFragment() {
}) })
} }
} }
val nsUploadOnly = sp.getBoolean(R.string.key_ns_upload_only, true) || !buildHelper.isEngineeringMode() val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_temp_target, false) || !buildHelper.isEngineeringMode()
if (nsUploadOnly) binding.refreshFromNightscout.visibility = View.INVISIBLE if (nsUploadOnly) binding.refreshFromNightscout.visibility = View.INVISIBLE
binding.showInvalidated.setOnCheckedChangeListener { _, _ -> binding.showInvalidated.setOnCheckedChangeListener { _, _ ->
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())
@ -151,7 +149,7 @@ class TreatmentsTempTargetFragment : DaggerFragment() {
_binding = null _binding = null
} }
private inner class RecyclerViewAdapter internal constructor(private var tempTargetList: List<TemporaryTarget>) : RecyclerView.Adapter<TempTargetsViewHolder>() { private inner class RecyclerViewAdapter(private var tempTargetList: List<TemporaryTarget>) : RecyclerView.Adapter<TempTargetsViewHolder>() {
private val dbRecord = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() private val dbRecord = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
private val currentlyActiveTarget = if (dbRecord is ValueWrapper.Existing) dbRecord.value else null private val currentlyActiveTarget = if (dbRecord is ValueWrapper.Existing) dbRecord.value else null

View file

@ -1,4 +1,4 @@
package info.nightscout.androidaps.plugins.treatments.fragments package info.nightscout.androidaps.activities.fragments
import android.content.DialogInterface import android.content.DialogInterface
import android.graphics.Paint import android.graphics.Paint
@ -12,12 +12,15 @@ import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.IobTotal import info.nightscout.androidaps.data.IobTotal
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.ExtendedBolus
import info.nightscout.androidaps.database.entities.TemporaryBasal import info.nightscout.androidaps.database.entities.TemporaryBasal
import info.nightscout.androidaps.database.entities.UserEntry.* import info.nightscout.androidaps.database.entities.UserEntry.*
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.interfaces.end import info.nightscout.androidaps.database.interfaces.end
import info.nightscout.androidaps.database.transactions.InvalidateExtendedBolusTransaction
import info.nightscout.androidaps.database.transactions.InvalidateTemporaryBasalTransaction import info.nightscout.androidaps.database.transactions.InvalidateTemporaryBasalTransaction
import info.nightscout.androidaps.databinding.TreatmentsTempbasalsFragmentBinding import info.nightscout.androidaps.databinding.TreatmentsTempbasalsFragmentBinding
import info.nightscout.androidaps.databinding.TreatmentsTempbasalsItemBinding import info.nightscout.androidaps.databinding.TreatmentsTempbasalsItemBinding
@ -33,7 +36,7 @@ import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsTemporaryBasalsFragment.RecyclerViewAdapter.TempBasalsViewHolder import info.nightscout.androidaps.activities.fragments.TreatmentsTemporaryBasalsFragment.RecyclerViewAdapter.TempBasalsViewHolder
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.T
@ -42,6 +45,7 @@ import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.rx.AapsSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.plusAssign
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.abs import kotlin.math.abs
@ -194,21 +198,41 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() {
init { init {
binding.remove.setOnClickListener { v: View -> binding.remove.setOnClickListener { v: View ->
val tempBasal = v.tag as TemporaryBasal val tempBasal = v.tag as TemporaryBasal
var extendedBolus: ExtendedBolus? = null
val isFakeExtended = tempBasal.type == TemporaryBasal.Type.FAKE_EXTENDED
if (isFakeExtended) {
val eb = repository.getExtendedBolusActiveAt(tempBasal.timestamp).blockingGet()
extendedBolus = if (eb is ValueWrapper.Existing) eb.value else null
}
val profile = profileFunction.getProfile(dateUtil.now()) val profile = profileFunction.getProfile(dateUtil.now())
?: return@setOnClickListener ?: return@setOnClickListener
context?.let { context?.let {
OKDialog.showConfirmation(it, resourceHelper.gs(R.string.removerecord), OKDialog.showConfirmation(it, resourceHelper.gs(R.string.removerecord),
""" """
${resourceHelper.gs(R.string.tempbasal_label)}: ${tempBasal.toStringFull(profile, dateUtil)} ${if (isFakeExtended) resourceHelper.gs(R.string.extended_bolus) else resourceHelper.gs(R.string.tempbasal_label)}: ${tempBasal.toStringFull(profile, dateUtil)}
${resourceHelper.gs(R.string.date)}: ${dateUtil.dateAndTimeString(tempBasal.timestamp)} ${resourceHelper.gs(R.string.date)}: ${dateUtil.dateAndTimeString(tempBasal.timestamp)}
""".trimIndent(), """.trimIndent(),
{ _: DialogInterface?, _: Int -> { _: DialogInterface?, _: Int ->
if (isFakeExtended && extendedBolus != null) {
uel.log(Action.EXTENDED_BOLUS_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(extendedBolus.timestamp),
ValueWithUnit.Insulin(extendedBolus.amount),
ValueWithUnit.UnitPerHour(extendedBolus.rate),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(extendedBolus.duration).toInt()))
disposable += repository.runTransactionForResult(InvalidateExtendedBolusTransaction(extendedBolus.id))
.subscribe(
{ aapsLogger.debug(LTag.DATABASE, "Removed extended bolus $extendedBolus") },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating extended bolus", it) })
} else if (!isFakeExtended) {
uel.log(Action.TEMP_BASAL_REMOVED, Sources.Treatments, uel.log(Action.TEMP_BASAL_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(tempBasal.timestamp)) ValueWithUnit.Timestamp(tempBasal.timestamp),
if (tempBasal.isAbsolute) ValueWithUnit.UnitPerHour(tempBasal.rate) else ValueWithUnit.Percent(tempBasal.rate.toInt()),
ValueWithUnit.Minute(T.msecs(tempBasal.duration).mins().toInt()))
disposable += repository.runTransactionForResult(InvalidateTemporaryBasalTransaction(tempBasal.id)) disposable += repository.runTransactionForResult(InvalidateTemporaryBasalTransaction(tempBasal.id))
.subscribe( .subscribe(
{ aapsLogger.debug(LTag.DATABASE, "Removed temporary basal $tempBasal") }, { aapsLogger.debug(LTag.DATABASE, "Removed temporary basal $tempBasal") },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating temporary basal", it) }) { aapsLogger.error(LTag.DATABASE, "Error while invalidating temporary basal", it) })
}
}, null) }, null)
} }
} }

View file

@ -1,4 +1,4 @@
package info.nightscout.androidaps.plugins.treatments.fragments package info.nightscout.androidaps.activities.fragments
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater

View file

@ -1,7 +1,5 @@
package info.nightscout.androidaps.data.defaultProfile package info.nightscout.androidaps.data.defaultProfile
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.data.ProfileImplOld
import info.nightscout.androidaps.data.PureProfile import info.nightscout.androidaps.data.PureProfile
import info.nightscout.androidaps.extensions.pureProfileFromJson import info.nightscout.androidaps.extensions.pureProfileFromJson
import info.nightscout.androidaps.interfaces.GlucoseUnit import info.nightscout.androidaps.interfaces.GlucoseUnit

View file

@ -1,7 +1,6 @@
package info.nightscout.androidaps.data.defaultProfile package info.nightscout.androidaps.data.defaultProfile
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.data.ProfileImplOld
import info.nightscout.androidaps.data.PureProfile import info.nightscout.androidaps.data.PureProfile
import info.nightscout.androidaps.extensions.pureProfileFromJson import info.nightscout.androidaps.extensions.pureProfileFromJson
import info.nightscout.androidaps.interfaces.GlucoseUnit import info.nightscout.androidaps.interfaces.GlucoseUnit

View file

@ -75,12 +75,16 @@ class CompatDBHelper @Inject constructor(
rxBus.send(EventFoodDatabaseChanged()) rxBus.send(EventFoodDatabaseChanged())
} }
it.filterIsInstance<ProfileSwitch>().firstOrNull()?.let { it.filterIsInstance<ProfileSwitch>().firstOrNull()?.let {
aapsLogger.debug(LTag.DATABASE, "Firing EventProfileNeedsUpdate") aapsLogger.debug(LTag.DATABASE, "Firing EventProfileSwitchChanged")
rxBus.send(EventProfileSwitchChanged()) rxBus.send(EventProfileSwitchChanged())
} }
it.filterIsInstance<EffectiveProfileSwitch>().firstOrNull()?.let { it.filterIsInstance<EffectiveProfileSwitch>().firstOrNull()?.let {
aapsLogger.debug(LTag.DATABASE, "Firing EventProfileNeedsUpdate") aapsLogger.debug(LTag.DATABASE, "Firing EventProfileSwitchChanged")
rxBus.send(EventProfileSwitchChanged()) rxBus.send(EventProfileSwitchChanged())
} }
it.filterIsInstance<OfflineEvent>().firstOrNull()?.let {
aapsLogger.debug(LTag.DATABASE, "Firing EventOfflineChange")
rxBus.send(EventOfflineChange())
}
} }
} }

View file

@ -4,8 +4,6 @@ import android.content.Context;
import android.database.DatabaseUtils; import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import androidx.annotation.Nullable;
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper; import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.dao.CloseableIterator; import com.j256.ormlite.dao.CloseableIterator;
import com.j256.ormlite.dao.Dao; import com.j256.ormlite.dao.Dao;
@ -20,19 +18,14 @@ import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import javax.inject.Inject; import javax.inject.Inject;
import info.nightscout.androidaps.events.EventRefreshOverview; import info.nightscout.androidaps.events.EventRefreshOverview;
import info.nightscout.androidaps.interfaces.ActivePlugin; import info.nightscout.androidaps.interfaces.ActivePlugin;
import info.nightscout.androidaps.interfaces.DatabaseHelperInterface;
import info.nightscout.androidaps.logging.AAPSLogger; import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.logging.LTag; import info.nightscout.androidaps.logging.LTag;
import info.nightscout.androidaps.plugins.bus.RxBusWrapper; import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload;
import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader; import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader;
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin; import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin;
import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.DateUtil;
@ -51,12 +44,9 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
@Inject VirtualPumpPlugin virtualPumpPlugin; @Inject VirtualPumpPlugin virtualPumpPlugin;
@Inject OpenHumansUploader openHumansUploader; @Inject OpenHumansUploader openHumansUploader;
@Inject ActivePlugin activePlugin; @Inject ActivePlugin activePlugin;
@Inject NSUpload nsUpload;
@Inject DateUtil dateUtil; @Inject DateUtil dateUtil;
public static final String DATABASE_NAME = "AndroidAPSDb"; public static final String DATABASE_NAME = "AndroidAPSDb";
public static final String DATABASE_DANARHISTORY = "DanaRHistory";
public static final String DATABASE_DBREQUESTS = "DBRequests";
private static final int DATABASE_VERSION = 13; private static final int DATABASE_VERSION = 13;
@ -76,19 +66,8 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) { public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) {
try { try {
aapsLogger.info(LTag.DATABASE, "onCreate"); aapsLogger.info(LTag.DATABASE, "onCreate");
TableUtils.createTableIfNotExists(connectionSource, DanaRHistoryRecord.class);
TableUtils.createTableIfNotExists(connectionSource, DbRequest.class);
TableUtils.createTableIfNotExists(connectionSource, TemporaryBasal.class);
TableUtils.createTableIfNotExists(connectionSource, ExtendedBolus.class);
TableUtils.createTableIfNotExists(connectionSource, InsightHistoryOffset.class);
TableUtils.createTableIfNotExists(connectionSource, InsightBolusID.class);
TableUtils.createTableIfNotExists(connectionSource, InsightPumpID.class);
TableUtils.createTableIfNotExists(connectionSource, OmnipodHistoryRecord.class); TableUtils.createTableIfNotExists(connectionSource, OmnipodHistoryRecord.class);
TableUtils.createTableIfNotExists(connectionSource, OHQueueItem.class); TableUtils.createTableIfNotExists(connectionSource, OHQueueItem.class);
database.execSQL("INSERT INTO sqlite_sequence (name, seq) SELECT \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_BOLUS_IDS + "\", " + System.currentTimeMillis() + " " +
"WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_BOLUS_IDS + "\")");
database.execSQL("INSERT INTO sqlite_sequence (name, seq) SELECT \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_PUMP_IDS + "\", " + System.currentTimeMillis() + " " +
"WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_PUMP_IDS + "\")");
} catch (SQLException e) { } catch (SQLException e) {
aapsLogger.error("Can't create database", e); aapsLogger.error("Can't create database", e);
throw new RuntimeException(e); throw new RuntimeException(e);
@ -103,22 +82,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
if (oldVersion < 7) { if (oldVersion < 7) {
aapsLogger.info(LTag.DATABASE, "onUpgrade"); aapsLogger.info(LTag.DATABASE, "onUpgrade");
TableUtils.dropTable(connectionSource, DanaRHistoryRecord.class, true);
TableUtils.dropTable(connectionSource, DbRequest.class, true);
TableUtils.dropTable(connectionSource, TemporaryBasal.class, true);
TableUtils.dropTable(connectionSource, ExtendedBolus.class, true);
onCreate(database, connectionSource); onCreate(database, connectionSource);
} else if (oldVersion < 10) {
TableUtils.createTableIfNotExists(connectionSource, InsightHistoryOffset.class);
TableUtils.createTableIfNotExists(connectionSource, InsightBolusID.class);
TableUtils.createTableIfNotExists(connectionSource, InsightPumpID.class);
database.execSQL("INSERT INTO sqlite_sequence (name, seq) SELECT \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_BOLUS_IDS + "\", " + System.currentTimeMillis() + " " +
"WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_BOLUS_IDS + "\")");
database.execSQL("INSERT INTO sqlite_sequence (name, seq) SELECT \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_PUMP_IDS + "\", " + System.currentTimeMillis() + " " +
"WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_PUMP_IDS + "\")");
} else if (oldVersion < 11) {
database.execSQL("UPDATE sqlite_sequence SET seq = " + System.currentTimeMillis() + " WHERE name = \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_BOLUS_IDS + "\"");
database.execSQL("UPDATE sqlite_sequence SET seq = " + System.currentTimeMillis() + " WHERE name = \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_PUMP_IDS + "\"");
} }
TableUtils.createTableIfNotExists(connectionSource, OHQueueItem.class); TableUtils.createTableIfNotExists(connectionSource, OHQueueItem.class);
} catch (SQLException e) { } catch (SQLException e) {
@ -149,15 +113,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
public void resetDatabases() { public void resetDatabases() {
try { try {
TableUtils.dropTable(connectionSource, DanaRHistoryRecord.class, true);
TableUtils.dropTable(connectionSource, DbRequest.class, true);
TableUtils.dropTable(connectionSource, TemporaryBasal.class, true);
TableUtils.dropTable(connectionSource, ExtendedBolus.class, true);
TableUtils.dropTable(connectionSource, OmnipodHistoryRecord.class, true); TableUtils.dropTable(connectionSource, OmnipodHistoryRecord.class, true);
TableUtils.createTableIfNotExists(connectionSource, DanaRHistoryRecord.class);
TableUtils.createTableIfNotExists(connectionSource, DbRequest.class);
TableUtils.createTableIfNotExists(connectionSource, TemporaryBasal.class);
TableUtils.createTableIfNotExists(connectionSource, ExtendedBolus.class);
TableUtils.createTableIfNotExists(connectionSource, OmnipodHistoryRecord.class); TableUtils.createTableIfNotExists(connectionSource, OmnipodHistoryRecord.class);
updateEarliestDataChange(0); updateEarliestDataChange(0);
} catch (SQLException e) { } catch (SQLException e) {
@ -177,34 +133,6 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
// ------------------ getDao ------------------------------------------- // ------------------ getDao -------------------------------------------
private Dao<DanaRHistoryRecord, String> getDaoDanaRHistory() throws SQLException {
return getDao(DanaRHistoryRecord.class);
}
private Dao<DbRequest, String> getDaoDbRequest() throws SQLException {
return getDao(DbRequest.class);
}
private Dao<TemporaryBasal, Long> getDaoTemporaryBasal() throws SQLException {
return getDao(TemporaryBasal.class);
}
private Dao<ExtendedBolus, Long> getDaoExtendedBolus() throws SQLException {
return getDao(ExtendedBolus.class);
}
private Dao<InsightPumpID, Long> getDaoInsightPumpID() throws SQLException {
return getDao(InsightPumpID.class);
}
private Dao<InsightBolusID, Long> getDaoInsightBolusID() throws SQLException {
return getDao(InsightBolusID.class);
}
private Dao<InsightHistoryOffset, String> getDaoInsightHistoryOffset() throws SQLException {
return getDao(InsightHistoryOffset.class);
}
private Dao<OmnipodHistoryRecord, Long> getDaoPodHistory() throws SQLException { private Dao<OmnipodHistoryRecord, Long> getDaoPodHistory() throws SQLException {
return getDao(OmnipodHistoryRecord.class); return getDao(OmnipodHistoryRecord.class);
} }
@ -213,76 +141,6 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return getDao(OHQueueItem.class); return getDao(OHQueueItem.class);
} }
public long roundDateToSec(long date) {
long rounded = date - date % 1000;
if (rounded != date)
aapsLogger.debug(LTag.DATABASE, "Rounding " + date + " to " + rounded);
return rounded;
}
// ------------- DbRequests handling -------------------
public void create(DbRequest dbr) throws SQLException {
getDaoDbRequest().create(dbr);
}
public int delete(DbRequest dbr) {
try {
return getDaoDbRequest().delete(dbr);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return 0;
}
public int deleteDbRequest(String nsClientId) {
try {
return getDaoDbRequest().deleteById(nsClientId);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return 0;
}
public void deleteDbRequestbyMongoId(String action, String id) {
try {
QueryBuilder<DbRequest, String> queryBuilder = getDaoDbRequest().queryBuilder();
// By nsID
Where where = queryBuilder.where();
where.eq("_id", id).and().eq("action", action);
queryBuilder.limit(10L);
PreparedQuery<DbRequest> preparedQuery = queryBuilder.prepare();
List<DbRequest> dbList = getDaoDbRequest().query(preparedQuery);
for (DbRequest r : dbList) delete(r);
// By nsClientID
where = queryBuilder.where();
where.eq("nsClientID", id).and().eq("action", action);
queryBuilder.limit(10L);
preparedQuery = queryBuilder.prepare();
dbList = getDaoDbRequest().query(preparedQuery);
for (DbRequest r : dbList) delete(r);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public void deleteAllDbRequests() {
try {
TableUtils.clearTable(connectionSource, DbRequest.class);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public CloseableIterator getDbRequestIterator() {
try {
return getDaoDbRequest().closeableIterator();
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
return null;
}
}
public static void updateEarliestDataChange(long newDate) { public static void updateEarliestDataChange(long newDate) {
if (earliestDataChange == null) { if (earliestDataChange == null) {
earliestDataChange = newDate; earliestDataChange = newDate;
@ -293,495 +151,6 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
} }
} }
// ----------------- DanaRHistory handling --------------------
public void createOrUpdate(DanaRHistoryRecord record) {
try {
getDaoDanaRHistory().createOrUpdate(record);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public List<DanaRHistoryRecord> getDanaRHistoryRecordsByType(byte type) {
List<DanaRHistoryRecord> historyList;
try {
QueryBuilder<DanaRHistoryRecord, String> queryBuilder = getDaoDanaRHistory().queryBuilder();
queryBuilder.orderBy("recordDate", false);
Where where = queryBuilder.where();
where.eq("recordCode", type);
queryBuilder.limit(200L);
PreparedQuery<DanaRHistoryRecord> preparedQuery = queryBuilder.prepare();
historyList = getDaoDanaRHistory().query(preparedQuery);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
historyList = new ArrayList<>();
}
return historyList;
}
// ------------ TemporaryBasal handling ---------------
//return true if new record was created
public boolean createOrUpdate(TemporaryBasal tempBasal) {
try {
TemporaryBasal old;
tempBasal.date = roundDateToSec(tempBasal.date);
if (tempBasal.source == Source.PUMP) {
// check for changed from pump change in NS
QueryBuilder<TemporaryBasal, Long> queryBuilder = getDaoTemporaryBasal().queryBuilder();
Where where = queryBuilder.where();
where.eq("pumpId", tempBasal.pumpId);
PreparedQuery<TemporaryBasal> preparedQuery = queryBuilder.prepare();
List<TemporaryBasal> trList = getDaoTemporaryBasal().query(preparedQuery);
if (trList.size() > 0) {
// do nothing, pump history record cannot be changed
aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: Already exists from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
return false;
}
// search by date (in case its standard record that has become pump record)
QueryBuilder<TemporaryBasal, Long> queryBuilder2 = getDaoTemporaryBasal().queryBuilder();
Where where2 = queryBuilder2.where();
where2.eq("date", tempBasal.date);
PreparedQuery<TemporaryBasal> preparedQuery2 = queryBuilder2.prepare();
List<TemporaryBasal> trList2 = getDaoTemporaryBasal().query(preparedQuery2);
if (trList2.size() > 0) {
old = trList2.get(0);
old.copyFromPump(tempBasal);
old.source = Source.PUMP;
aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: Updated record with Pump Data : " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
getDaoTemporaryBasal().update(old);
openHumansUploader.enqueueTemporaryBasal(old);
updateEarliestDataChange(tempBasal.date);
// scheduleTemporaryBasalChange();
return false;
}
getDaoTemporaryBasal().create(tempBasal);
openHumansUploader.enqueueTemporaryBasal(tempBasal);
aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
updateEarliestDataChange(tempBasal.date);
// scheduleTemporaryBasalChange();
return true;
}
if (tempBasal.source == Source.NIGHTSCOUT) {
old = getDaoTemporaryBasal().queryForId(tempBasal.date);
if (old != null) {
if (!old.isAbsolute && tempBasal.isAbsolute) { // converted to absolute by "ns_sync_use_absolute"
// so far ignore, do not convert back because it may not be accurate
return false;
}
if (!old.isEqual(tempBasal)) {
long oldDate = old.date;
getDaoTemporaryBasal().delete(old); // need to delete/create because date may change too
old.copyFrom(tempBasal);
getDaoTemporaryBasal().create(old);
openHumansUploader.enqueueTemporaryBasal(old);
aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: Updating record by date from: " + Source.getString(tempBasal.source) + " " + old.toString());
updateEarliestDataChange(oldDate);
updateEarliestDataChange(old.date);
// scheduleTemporaryBasalChange();
return true;
}
return false;
}
// find by NS _id
if (tempBasal._id != null) {
QueryBuilder<TemporaryBasal, Long> queryBuilder = getDaoTemporaryBasal().queryBuilder();
Where where = queryBuilder.where();
where.eq("_id", tempBasal._id);
PreparedQuery<TemporaryBasal> preparedQuery = queryBuilder.prepare();
List<TemporaryBasal> trList = getDaoTemporaryBasal().query(preparedQuery);
if (trList.size() > 0) {
old = trList.get(0);
if (!old.isEqual(tempBasal)) {
long oldDate = old.date;
getDaoTemporaryBasal().delete(old); // need to delete/create because date may change too
old.copyFrom(tempBasal);
getDaoTemporaryBasal().create(old);
openHumansUploader.enqueueTemporaryBasal(old);
aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: Updating record by _id from: " + Source.getString(tempBasal.source) + " " + old.toString());
updateEarliestDataChange(oldDate);
updateEarliestDataChange(old.date);
// scheduleTemporaryBasalChange();
return true;
}
}
}
getDaoTemporaryBasal().create(tempBasal);
openHumansUploader.enqueueTemporaryBasal(tempBasal);
aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
updateEarliestDataChange(tempBasal.date);
// scheduleTemporaryBasalChange();
return true;
}
if (tempBasal.source == Source.USER) {
getDaoTemporaryBasal().create(tempBasal);
openHumansUploader.enqueueTemporaryBasal(tempBasal);
aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
updateEarliestDataChange(tempBasal.date);
// scheduleTemporaryBasalChange();
return true;
}
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return false;
}
public void delete(TemporaryBasal tempBasal) {
try {
getDaoTemporaryBasal().delete(tempBasal);
openHumansUploader.enqueueTemporaryBasal(tempBasal, true);
updateEarliestDataChange(tempBasal.date);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
// scheduleTemporaryBasalChange();
}
public List<TemporaryBasal> getTemporaryBasalsDataFromTime(long mills, boolean ascending) {
try {
List<TemporaryBasal> tempbasals;
QueryBuilder<TemporaryBasal, Long> queryBuilder = getDaoTemporaryBasal().queryBuilder();
queryBuilder.orderBy("date", ascending);
Where where = queryBuilder.where();
where.ge("date", mills);
PreparedQuery<TemporaryBasal> preparedQuery = queryBuilder.prepare();
tempbasals = getDaoTemporaryBasal().query(preparedQuery);
return tempbasals;
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return new ArrayList<TemporaryBasal>();
}
/*
{
"_id": "59232e1ddd032d04218dab00",
"eventType": "Temp Basal",
"duration": 60,
"percent": -50,
"created_at": "2017-05-22T18:29:57Z",
"enteredBy": "AndroidAPS",
"notes": "Basal Temp Start 50% 60.0 min",
"NSCLIENT_ID": 1495477797863,
"mills": 1495477797000,
"mgdl": 194.5,
"endmills": 1495481397000
}
*/
public TemporaryBasal findTempBasalByPumpId(Long pumpId) {
try {
QueryBuilder<TemporaryBasal, Long> queryBuilder = null;
queryBuilder = getDaoTemporaryBasal().queryBuilder();
queryBuilder.orderBy("date", false);
Where where = queryBuilder.where();
where.eq("pumpId", pumpId);
PreparedQuery<TemporaryBasal> preparedQuery = queryBuilder.prepare();
List<TemporaryBasal> list = getDaoTemporaryBasal().query(preparedQuery);
if (list.size() > 0)
return list.get(0);
else
return null;
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return null;
}
// ------------ ExtendedBolus handling ---------------
public ExtendedBolus getExtendedBolusByPumpId(long pumpId) {
try {
return getDaoExtendedBolus().queryBuilder()
.where().eq("pumpId", pumpId)
.queryForFirst();
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return null;
}
public void delete(ExtendedBolus extendedBolus) {
try {
getDaoExtendedBolus().delete(extendedBolus);
openHumansUploader.enqueueExtendedBolus(extendedBolus, true);
updateEarliestDataChange(extendedBolus.date);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
// scheduleExtendedBolusChange();
}
/*
{
"_id": "5924898d577eb0880e355337",
"eventType": "Combo Bolus",
"duration": 120,
"splitNow": 0,
"splitExt": 100,
"enteredinsulin": 1,
"relative": 1,
"created_at": "2017-05-23T19:12:14Z",
"enteredBy": "AndroidAPS",
"NSCLIENT_ID": 1495566734628,
"mills": 1495566734000,
"mgdl": 106
}
*/
// ---------------- ProfileSwitch handling ---------------
/*
public boolean createOrUpdate(ProfileSwitch profileSwitch) {
try {
ProfileSwitch old;
profileSwitch.date = roundDateToSec(profileSwitch.date);
if (profileSwitch.source == Source.NIGHTSCOUT) {
old = getDaoProfileSwitch().queryForId(profileSwitch.date);
if (old != null) {
if (!old.isEqual(profileSwitch)) {
profileSwitch.source = old.source;
profileSwitch.profileName = old.profileName; // preserver profileName to prevent multiple CPP extension
getDaoProfileSwitch().delete(old); // need to delete/create because date may change too
getDaoProfileSwitch().create(profileSwitch);
aapsLogger.debug(LTag.DATABASE, "PROFILESWITCH: Updating record by date from: " + Source.getString(profileSwitch.source) + " " + old.toString());
openHumansUploader.enqueueProfileSwitch(profileSwitch);
scheduleProfileSwitchChange();
return true;
}
return false;
}
// find by NS _id
if (profileSwitch._id != null) {
QueryBuilder<ProfileSwitch, Long> queryBuilder = getDaoProfileSwitch().queryBuilder();
Where where = queryBuilder.where();
where.eq("_id", profileSwitch._id);
PreparedQuery<ProfileSwitch> preparedQuery = queryBuilder.prepare();
List<ProfileSwitch> trList = getDaoProfileSwitch().query(preparedQuery);
if (trList.size() > 0) {
old = trList.get(0);
if (!old.isEqual(profileSwitch)) {
getDaoProfileSwitch().delete(old); // need to delete/create because date may change too
old.copyFrom(profileSwitch);
getDaoProfileSwitch().create(old);
aapsLogger.debug(LTag.DATABASE, "PROFILESWITCH: Updating record by _id from: " + Source.getString(profileSwitch.source) + " " + old.toString());
openHumansUploader.enqueueProfileSwitch(old);
scheduleProfileSwitchChange();
return true;
}
}
}
// look for already added percentage from NS
profileSwitch.profileName = PercentageSplitter.INSTANCE.pureName(profileSwitch.profileName);
getDaoProfileSwitch().create(profileSwitch);
aapsLogger.debug(LTag.DATABASE, "PROFILESWITCH: New record from: " + Source.getString(profileSwitch.source) + " " + profileSwitch.toString());
openHumansUploader.enqueueProfileSwitch(profileSwitch);
scheduleProfileSwitchChange();
return true;
}
if (profileSwitch.source == Source.USER) {
getDaoProfileSwitch().create(profileSwitch);
aapsLogger.debug(LTag.DATABASE, "PROFILESWITCH: New record from: " + Source.getString(profileSwitch.source) + " " + profileSwitch.toString());
openHumansUploader.enqueueProfileSwitch(profileSwitch);
scheduleProfileSwitchChange();
return true;
}
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return false;
}
public void delete(ProfileSwitch profileSwitch) {
try {
getDaoProfileSwitch().delete(profileSwitch);
openHumansUploader.enqueueProfileSwitch(profileSwitch, true);
scheduleProfileSwitchChange();
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
}
private void scheduleProfileSwitchChange() {
class PostRunnable implements Runnable {
public void run() {
aapsLogger.debug(LTag.DATABASE, "Firing EventProfileNeedsUpdate");
rxBus.send(new EventReloadProfileSwitchData());
rxBus.send(new EventProfileNeedsUpdate());
scheduledProfileSwitchEventPost = null;
}
}
// prepare task for execution in 1 sec
// cancel waiting task to prevent sending multiple posts
if (scheduledProfileSwitchEventPost != null)
scheduledProfileSwitchEventPost.cancel(false);
Runnable task = new PostRunnable();
final int sec = 1;
scheduledProfileSwitchEventPost = profileSwitchEventWorker.schedule(task, sec, TimeUnit.SECONDS);
}
*/
/*
{
"_id":"592fa43ed97496a80da913d2",
"created_at":"2017-06-01T05:20:06Z",
"eventType":"Profile Switch",
"profile":"2016 +30%",
"units":"mmol",
"enteredBy":"sony",
"NSCLIENT_ID":1496294454309,
}
*/
/*
public void createProfileSwitchFromJsonIfNotExists(JSONObject trJson) {
try {
ProfileSwitch profileSwitch = new ProfileSwitch(StaticInjector.Companion.getInstance());
profileSwitch.date = trJson.getLong("mills");
if (trJson.has("duration"))
profileSwitch.durationInMinutes = trJson.getInt("duration");
profileSwitch._id = trJson.getString("_id");
profileSwitch.profileName = trJson.getString("profile");
profileSwitch.isCPP = trJson.has("CircadianPercentageProfile");
profileSwitch.source = Source.NIGHTSCOUT;
if (trJson.has("timeshift"))
profileSwitch.timeshift = trJson.getInt("timeshift");
if (trJson.has("percentage"))
profileSwitch.percentage = trJson.getInt("percentage");
if (trJson.has("profileJson"))
profileSwitch.profileJson = trJson.getString("profileJson");
else {
ProfileSource profileSource = activePlugin.getActiveProfileSource();
ProfileStore store = profileSource.getProfile();
if (store != null) {
PureProfile profile = store.getSpecificProfile(profileSwitch.profileName);
if (profile != null) {
profileSwitch.profileJson = profile.getJsonObject().toString();
aapsLogger.debug(LTag.DATABASE, "Profile switch prefilled with JSON from local store");
// Update data in NS
nsUpload.updateProfileSwitch(profileSwitch, dateUtil);
} else {
aapsLogger.debug(LTag.DATABASE, "JSON for profile switch doesn't exist. Ignoring: " + trJson.toString());
return;
}
} else {
aapsLogger.debug(LTag.DATABASE, "Store for profile switch doesn't exist. Ignoring: " + trJson.toString());
return;
}
}
if (trJson.has("profilePlugin"))
profileSwitch.profilePlugin = trJson.getString("profilePlugin");
createOrUpdate(profileSwitch);
} catch (JSONException e) {
aapsLogger.error("Unhandled exception: " + trJson.toString(), e);
}
}
public void deleteProfileSwitchById(String _id) {
ProfileSwitch stored = findProfileSwitchById(_id);
if (stored != null) {
aapsLogger.debug(LTag.DATABASE, "PROFILESWITCH: Removing ProfileSwitch record from database: " + stored.toString());
delete(stored);
scheduleProfileSwitchChange();
}
}
public ProfileSwitch findProfileSwitchById(String _id) {
try {
QueryBuilder<ProfileSwitch, Long> queryBuilder = getDaoProfileSwitch().queryBuilder();
Where where = queryBuilder.where();
where.eq("_id", _id);
PreparedQuery<ProfileSwitch> preparedQuery = queryBuilder.prepare();
List<ProfileSwitch> list = getDaoProfileSwitch().query(preparedQuery);
if (list.size() == 1) {
return list.get(0);
} else {
return null;
}
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return null;
}
*/
// ---------------- Insight history handling ---------------
public void createOrUpdate(InsightHistoryOffset offset) {
try {
getDaoInsightHistoryOffset().createOrUpdate(offset);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public InsightHistoryOffset getInsightHistoryOffset(String pumpSerial) {
try {
return getDaoInsightHistoryOffset().queryForId(pumpSerial);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return null;
}
public void createOrUpdate(InsightBolusID bolusID) {
try {
getDaoInsightBolusID().createOrUpdate(bolusID);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public InsightBolusID getInsightBolusID(String pumpSerial, int bolusID, long timestamp) {
try {
return getDaoInsightBolusID().queryBuilder()
.where().eq("pumpSerial", pumpSerial)
.and().eq("bolusID", bolusID)
.and().between("timestamp", timestamp - 259200000, timestamp + 259200000)
.queryForFirst();
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return null;
}
public void createOrUpdate(InsightPumpID pumpID) {
try {
getDaoInsightPumpID().createOrUpdate(pumpID);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public InsightPumpID getPumpStoppedEvent(String pumpSerial, long before) {
try {
return getDaoInsightPumpID().queryBuilder()
.orderBy("timestamp", false)
.where().eq("pumpSerial", pumpSerial)
.and().in("eventType", "PumpStopped", "PumpPaused")
.and().lt("timestamp", before)
.queryForFirst();
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return null;
}
// ---------------- Food handling --------------- // ---------------- Food handling ---------------
// ---------------- PodHistory handling --------------- // ---------------- PodHistory handling ---------------
@ -924,14 +293,4 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
} }
return 0L; return 0L;
} }
public long getCountOfAllRows() {
try {
return getDaoExtendedBolus().countOf()
+ getDaoTemporaryBasal().countOf();
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return 0L;
}
} }

View file

@ -5,8 +5,6 @@ import androidx.annotation.Nullable;
import com.j256.ormlite.dao.CloseableIterator; import com.j256.ormlite.dao.CloseableIterator;
import org.json.JSONObject;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List; import java.util.List;
@ -23,61 +21,24 @@ public class DatabaseHelperProvider implements DatabaseHelperInterface {
@Inject DatabaseHelperProvider() { @Inject DatabaseHelperProvider() {
} }
@Override public void createOrUpdate(@NonNull DanaRHistoryRecord record) {
MainApp.Companion.getDbHelper().createOrUpdate(record);
}
@Override public void createOrUpdate(@NonNull OmnipodHistoryRecord record) { @Override public void createOrUpdate(@NonNull OmnipodHistoryRecord record) {
MainApp.Companion.getDbHelper().createOrUpdate(record); MainApp.Companion.getDbHelper().createOrUpdate(record);
} }
@NonNull @Override public List<DanaRHistoryRecord> getDanaRHistoryRecordsByType(byte type) {
return MainApp.Companion.getDbHelper().getDanaRHistoryRecordsByType(type);
}
@Override public long size(@NonNull String table) {
return MainApp.Companion.getDbHelper().size(table);
}
@Override public void create(@NonNull DbRequest record) {
try {
MainApp.Companion.getDbHelper().create(record);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override public void deleteAllDbRequests() {
MainApp.Companion.getDbHelper().deleteAllDbRequests();
}
@Override public int deleteDbRequest(@NonNull String id) {
return MainApp.Companion.getDbHelper().deleteDbRequest(id);
}
@Override public void deleteDbRequestbyMongoId(@NonNull String action, @NonNull String _id) {
MainApp.Companion.getDbHelper().deleteDbRequestbyMongoId(action, _id);
}
@NonNull @Override public CloseableIterator<DbRequest> getDbRequestIterator() {
return MainApp.Companion.getDbHelper().getDbRequestIterator();
}
@Override public long roundDateToSec(long date) {
return MainApp.Companion.getDbHelper().roundDateToSec(date);
}
@Override public boolean createOrUpdate(@NonNull TemporaryBasal tempBasal) { @Override public boolean createOrUpdate(@NonNull TemporaryBasal tempBasal) {
return MainApp.Companion.getDbHelper().createOrUpdate(tempBasal); // return MainApp.Companion.getDbHelper().createOrUpdate(tempBasal);
return false;
} }
@Nullable @Override public TemporaryBasal findTempBasalByPumpId(long id) { @Nullable @Override public TemporaryBasal findTempBasalByPumpId(long id) {
return MainApp.Companion.getDbHelper().findTempBasalByPumpId(id); // return MainApp.Companion.getDbHelper().findTempBasalByPumpId(id);
return null;
} }
@Deprecated @Deprecated
@NonNull @Override public List<TemporaryBasal> getTemporaryBasalsDataFromTime(long mills, boolean ascending) { @NonNull @Override public List<TemporaryBasal> getTemporaryBasalsDataFromTime(long mills, boolean ascending) {
return MainApp.Companion.getDbHelper().getTemporaryBasalsDataFromTime(mills, ascending); // return MainApp.Companion.getDbHelper().getTemporaryBasalsDataFromTime(mills, ascending);
return null;
} }
@NonNull @Override public List<OmnipodHistoryRecord> getAllOmnipodHistoryRecordsFromTimestamp(long timestamp, boolean ascending) { @NonNull @Override public List<OmnipodHistoryRecord> getAllOmnipodHistoryRecordsFromTimestamp(long timestamp, boolean ascending) {
@ -88,36 +49,13 @@ public class DatabaseHelperProvider implements DatabaseHelperInterface {
return MainApp.Companion.getDbHelper().findOmnipodHistoryRecordByPumpId(pumpId); return MainApp.Companion.getDbHelper().findOmnipodHistoryRecordByPumpId(pumpId);
} }
@Override public void createOrUpdate(@NonNull InsightBolusID record) {
MainApp.Companion.getDbHelper().createOrUpdate(record);
}
@Override public void createOrUpdate(@NonNull InsightPumpID record) {
MainApp.Companion.getDbHelper().createOrUpdate(record);
}
@Override public void createOrUpdate(@NonNull InsightHistoryOffset record) {
MainApp.Companion.getDbHelper().createOrUpdate(record);
}
@Override public void delete(@NonNull ExtendedBolus extendedBolus) { @Override public void delete(@NonNull ExtendedBolus extendedBolus) {
MainApp.Companion.getDbHelper().delete(extendedBolus); // MainApp.Companion.getDbHelper().delete(extendedBolus);
} }
@Nullable @Override public ExtendedBolus getExtendedBolusByPumpId(long pumpId) { @Nullable @Override public ExtendedBolus getExtendedBolusByPumpId(long pumpId) {
return MainApp.Companion.getDbHelper().getExtendedBolusByPumpId(pumpId); // return MainApp.Companion.getDbHelper().getExtendedBolusByPumpId(pumpId);
} return null;
@Nullable @Override public InsightBolusID getInsightBolusID(@NonNull String pumpSerial, int bolusID, long timestamp) {
return MainApp.Companion.getDbHelper().getInsightBolusID(pumpSerial, bolusID, timestamp);
}
@Nullable @Override public InsightHistoryOffset getInsightHistoryOffset(@NonNull String pumpSerial) {
return MainApp.Companion.getDbHelper().getInsightHistoryOffset(pumpSerial);
}
@Nullable @Override public InsightPumpID getPumpStoppedEvent(@NonNull String pumpSerial, long before) {
return MainApp.Companion.getDbHelper().getPumpStoppedEvent(pumpSerial, before);
} }
@Override public void resetDatabases() { @Override public void resetDatabases() {
@ -140,10 +78,6 @@ public class DatabaseHelperProvider implements DatabaseHelperInterface {
MainApp.Companion.getDbHelper().clearOpenHumansQueue(); MainApp.Companion.getDbHelper().clearOpenHumansQueue();
} }
@Override public long getCountOfAllRows() {
return MainApp.Companion.getDbHelper().getCountOfAllRows();
}
@Override public void removeAllOHQueueItemsWithIdSmallerThan(long id) { @Override public void removeAllOHQueueItemsWithIdSmallerThan(long id) {
MainApp.Companion.getDbHelper().removeAllOHQueueItemsWithIdSmallerThan(id); MainApp.Companion.getDbHelper().removeAllOHQueueItemsWithIdSmallerThan(id);
} }

View file

@ -4,7 +4,7 @@ import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.MainActivity import info.nightscout.androidaps.MainActivity
import info.nightscout.androidaps.activities.* 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.maintenance.activities.LogSettingActivity
import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansLoginActivity import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansLoginActivity
import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity
@ -15,6 +15,7 @@ import info.nightscout.androidaps.setupwizard.SetupWizardActivity
@Suppress("unused") @Suppress("unused")
abstract class ActivitiesModule { abstract class ActivitiesModule {
@ContributesAndroidInjector abstract fun contributesTreatmentsActivity(): TreatmentsActivity
@ContributesAndroidInjector abstract fun contributesHistoryBrowseActivity(): HistoryBrowseActivity @ContributesAndroidInjector abstract fun contributesHistoryBrowseActivity(): HistoryBrowseActivity
@ContributesAndroidInjector abstract fun contributesLogSettingActivity(): LogSettingActivity @ContributesAndroidInjector abstract fun contributesLogSettingActivity(): LogSettingActivity
@ContributesAndroidInjector abstract fun contributeMainActivity(): MainActivity @ContributesAndroidInjector abstract fun contributeMainActivity(): MainActivity

View file

@ -7,12 +7,15 @@ import dagger.android.AndroidInjector
import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.automation.di.AutomationModule import info.nightscout.androidaps.automation.di.AutomationModule
import info.nightscout.androidaps.combo.di.ComboModule import info.nightscout.androidaps.combo.di.ComboModule
import info.nightscout.androidaps.di.CoreModule import info.nightscout.androidaps.dana.di.DanaHistoryModule
import info.nightscout.androidaps.dana.di.DanaModule import info.nightscout.androidaps.dana.di.DanaModule
import info.nightscout.androidaps.danar.di.DanaRModule import info.nightscout.androidaps.danar.di.DanaRModule
import info.nightscout.androidaps.danars.di.DanaRSModule import info.nightscout.androidaps.danars.di.DanaRSModule
import info.nightscout.androidaps.danars.di.InsightModule
import info.nightscout.androidaps.database.DatabaseModule import info.nightscout.androidaps.database.DatabaseModule
import info.nightscout.androidaps.di.CoreModule
import info.nightscout.androidaps.insight.di.InsightDatabaseModule
import info.nightscout.androidaps.insight.di.InsightModule
import info.nightscout.androidaps.plugins.pump.common.di.PumpCommonModule
import info.nightscout.androidaps.plugins.pump.common.di.RileyLinkModule import info.nightscout.androidaps.plugins.pump.common.di.RileyLinkModule
import info.nightscout.androidaps.plugins.pump.medtronic.di.MedtronicModule import info.nightscout.androidaps.plugins.pump.medtronic.di.MedtronicModule
import info.nightscout.androidaps.plugins.pump.omnipod.dash.dagger.OmnipodDashModule import info.nightscout.androidaps.plugins.pump.omnipod.dash.dagger.OmnipodDashModule
@ -35,6 +38,7 @@ import javax.inject.Singleton
CommandQueueModule::class, CommandQueueModule::class,
ObjectivesModule::class, ObjectivesModule::class,
WizardModule::class, WizardModule::class,
PumpCommonModule::class,
RileyLinkModule::class, RileyLinkModule::class,
MedtronicModule::class, MedtronicModule::class,
OmnipodDashModule::class, OmnipodDashModule::class,
@ -47,10 +51,12 @@ import javax.inject.Singleton
UIModule::class, UIModule::class,
CoreModule::class, CoreModule::class,
DanaModule::class, DanaModule::class,
DanaHistoryModule::class,
DanaRModule::class, DanaRModule::class,
DanaRSModule::class, DanaRSModule::class,
ComboModule::class, ComboModule::class,
InsightModule::class, InsightModule::class,
InsightDatabaseModule::class,
WorkersModule::class, WorkersModule::class,
OHUploaderModule::class OHUploaderModule::class
] ]

View file

@ -6,32 +6,34 @@ import dagger.Lazy
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.db.DatabaseHelperProvider import info.nightscout.androidaps.db.DatabaseHelperProvider
import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
import info.nightscout.androidaps.plugins.configBuilder.PluginStore import info.nightscout.androidaps.plugins.configBuilder.PluginStore
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctionImplementation
import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefsImpl import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefsImpl
import info.nightscout.androidaps.plugins.general.nsclient.DataSyncSelectorImplementation import info.nightscout.androidaps.plugins.general.nsclient.DataSyncSelectorImplementation
import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
import info.nightscout.androidaps.plugins.pump.PumpSyncImplementation import info.nightscout.androidaps.plugins.pump.PumpSyncImplementation
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import info.nightscout.androidaps.queue.CommandQueue import info.nightscout.androidaps.queue.CommandQueue
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.androidNotification.NotificationHolderImpl import info.nightscout.androidaps.utils.androidNotification.NotificationHolderImpl
import info.nightscout.androidaps.utils.buildHelper.ConfigImpl import info.nightscout.androidaps.utils.buildHelper.ConfigImpl
import info.nightscout.androidaps.utils.resources.IconsProviderImplementation import info.nightscout.androidaps.utils.resources.IconsProviderImplementation
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.rx.DefaultAapsSchedulers import info.nightscout.androidaps.utils.rx.DefaultAapsSchedulers
import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.sharedPreferences.SP
import info.nightscout.androidaps.utils.storage.FileStorage import info.nightscout.androidaps.utils.storage.FileStorage
import info.nightscout.androidaps.utils.storage.Storage import info.nightscout.androidaps.utils.storage.Storage
import javax.inject.Singleton import javax.inject.Singleton
@Suppress("unused") @Suppress("unused")
@Module(includes = [ @Module(includes = [
AppModule.AppBindings::class AppModule.AppBindings::class
@ -64,32 +66,32 @@ open class AppModule {
@Provides @Provides
@Singleton @Singleton
fun providesUploadQueue( fun provideProfileFunction(aapsLogger: AAPSLogger, sp: SP, resourceHelper: ResourceHelper, activePlugin: ActivePlugin, repository: AppRepository, dateUtil: DateUtil): ProfileFunction {
aapsLogger: AAPSLogger, return ProfileFunctionImplementation(aapsLogger, sp, resourceHelper, activePlugin, repository, dateUtil)
databaseHelper: DatabaseHelperInterface, }
context: Context,
sp: SP,
rxBus: RxBusWrapper
): UploadQueueAdminInterface = UploadQueue(aapsLogger, databaseHelper, context, sp, rxBus)
@Module @Module
interface AppBindings { interface AppBindings {
@Binds fun bindContext(mainApp: MainApp): Context @Binds fun bindContext(mainApp: MainApp): Context
@Binds fun bindInjector(mainApp: MainApp): HasAndroidInjector @Binds fun bindInjector(mainApp: MainApp): HasAndroidInjector
@Binds fun bindActivePluginProvider(pluginStore: PluginStore): ActivePlugin @Binds fun bindActivePluginProvider(pluginStore: PluginStore): ActivePlugin
@Binds fun bindCommandQueueProvider(commandQueue: CommandQueue): CommandQueueProvider @Binds fun bindCommandQueueProvider(commandQueue: CommandQueue): CommandQueueProvider
@Binds fun bindConfigInterface(config: ConfigImpl): Config @Binds fun bindConfigInterface(config: ConfigImpl): Config
@Binds fun bindConfigBuilderInterface(configBuilderPlugin: ConfigBuilderPlugin): ConfigBuilder
@Binds
fun bindConfigBuilderInterface(configBuilderPlugin: ConfigBuilderPlugin): ConfigBuilder
@Binds fun bindTreatmentsInterface(treatmentsPlugin: TreatmentsPlugin): TreatmentsInterface @Binds fun bindTreatmentsInterface(treatmentsPlugin: TreatmentsPlugin): TreatmentsInterface
@Binds fun bindDatabaseHelperInterface(databaseHelperProvider: DatabaseHelperProvider): DatabaseHelperInterface @Binds fun bindDatabaseHelperInterface(databaseHelperProvider: DatabaseHelperProvider): DatabaseHelperInterface
@Binds fun bindNotificationHolderInterface(notificationHolder: NotificationHolderImpl): NotificationHolder @Binds fun bindNotificationHolderInterface(notificationHolder: NotificationHolderImpl): NotificationHolder
@Binds fun bindImportExportPrefsInterface(importExportPrefs: ImportExportPrefsImpl): ImportExportPrefs @Binds fun bindImportExportPrefsInterface(importExportPrefs: ImportExportPrefsImpl): ImportExportPrefs
@Binds fun bindIconsProviderInterface(iconsProvider: IconsProviderImplementation): IconsProvider @Binds fun bindIconsProviderInterface(iconsProvider: IconsProviderImplementation): IconsProvider
@Binds fun bindLoopInterface(loopPlugin: LoopPlugin): LoopInterface @Binds fun bindLoopInterface(loopPlugin: LoopPlugin): Loop
@Binds fun bindIobCobCalculatorInterface(iobCobCalculatorPlugin: IobCobCalculatorPlugin): IobCobCalculator @Binds fun bindIobCobCalculatorInterface(iobCobCalculatorPlugin: IobCobCalculatorPlugin): IobCobCalculator
@Binds fun bindSmsCommunicatorInterface(smsCommunicatorPlugin: SmsCommunicatorPlugin): SmsCommunicator @Binds fun bindSmsCommunicatorInterface(smsCommunicatorPlugin: SmsCommunicatorPlugin): SmsCommunicator
@Binds fun bindUploadQueueAdminInterfaceToUploadQueue(uploadQueueAdminInterface: UploadQueueAdminInterface) : UploadQueueInterface
@Binds fun bindDataSyncSelector(dataSyncSelectorImplementation: DataSyncSelectorImplementation): DataSyncSelector @Binds fun bindDataSyncSelector(dataSyncSelectorImplementation: DataSyncSelectorImplementation): DataSyncSelector
@Binds fun bindPumpSync(pumpSyncImplementation: PumpSyncImplementation): PumpSync @Binds fun bindPumpSync(pumpSyncImplementation: PumpSyncImplementation): PumpSync
} }

View file

@ -29,11 +29,9 @@ import info.nightscout.androidaps.plugins.general.tidepool.TidepoolFragment
import info.nightscout.androidaps.plugins.general.wear.WearFragment import info.nightscout.androidaps.plugins.general.wear.WearFragment
import info.nightscout.androidaps.plugins.insulin.InsulinFragment import info.nightscout.androidaps.plugins.insulin.InsulinFragment
import info.nightscout.androidaps.plugins.profile.local.LocalProfileFragment import info.nightscout.androidaps.plugins.profile.local.LocalProfileFragment
import info.nightscout.androidaps.plugins.profile.ns.NSProfileFragment
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpFragment import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpFragment
import info.nightscout.androidaps.plugins.source.BGSourceFragment import info.nightscout.androidaps.plugins.source.BGSourceFragment
import info.nightscout.androidaps.plugins.treatments.TreatmentsFragment import info.nightscout.androidaps.activities.fragments.*
import info.nightscout.androidaps.plugins.treatments.fragments.*
import info.nightscout.androidaps.utils.protection.PasswordCheck import info.nightscout.androidaps.utils.protection.PasswordCheck
@Module @Module
@ -46,8 +44,7 @@ abstract class FragmentsModule {
@ContributesAndroidInjector abstract fun contributesAutomationFragment(): AutomationFragment @ContributesAndroidInjector abstract fun contributesAutomationFragment(): AutomationFragment
@ContributesAndroidInjector abstract fun contributesBGSourceFragment(): BGSourceFragment @ContributesAndroidInjector abstract fun contributesBGSourceFragment(): BGSourceFragment
@ContributesAndroidInjector @ContributesAndroidInjector abstract fun contributesConfigBuilderFragment(): ConfigBuilderFragment
abstract fun contributesConfigBuilderFragment(): ConfigBuilderFragment
@ContributesAndroidInjector abstract fun contributesFoodFragment(): FoodFragment @ContributesAndroidInjector abstract fun contributesFoodFragment(): FoodFragment
@ContributesAndroidInjector abstract fun contributesInsulinFragment(): InsulinFragment @ContributesAndroidInjector abstract fun contributesInsulinFragment(): InsulinFragment
@ -58,14 +55,11 @@ abstract class FragmentsModule {
@ContributesAndroidInjector abstract fun contributesOverviewFragment(): OverviewFragment @ContributesAndroidInjector abstract fun contributesOverviewFragment(): OverviewFragment
@ContributesAndroidInjector abstract fun contributesLoopFragment(): LoopFragment @ContributesAndroidInjector abstract fun contributesLoopFragment(): LoopFragment
@ContributesAndroidInjector abstract fun contributesMaintenanceFragment(): MaintenanceFragment @ContributesAndroidInjector abstract fun contributesMaintenanceFragment(): MaintenanceFragment
@ContributesAndroidInjector abstract fun contributesNSProfileFragment(): NSProfileFragment
@ContributesAndroidInjector abstract fun contributesNSClientFragment(): NSClientFragment @ContributesAndroidInjector abstract fun contributesNSClientFragment(): NSClientFragment
@ContributesAndroidInjector @ContributesAndroidInjector abstract fun contributesSmsCommunicatorFragment(): SmsCommunicatorFragment
abstract fun contributesSmsCommunicatorFragment(): SmsCommunicatorFragment
@ContributesAndroidInjector abstract fun contributesWearFragment(): WearFragment @ContributesAndroidInjector abstract fun contributesWearFragment(): WearFragment
@ContributesAndroidInjector abstract fun contributesTidepoolFragment(): TidepoolFragment @ContributesAndroidInjector abstract fun contributesTidepoolFragment(): TidepoolFragment
@ContributesAndroidInjector abstract fun contributesTreatmentsFragment(): TreatmentsFragment
@ContributesAndroidInjector abstract fun contributesTreatmentsBolusFragment(): TreatmentsBolusCarbsFragment @ContributesAndroidInjector abstract fun contributesTreatmentsBolusFragment(): TreatmentsBolusCarbsFragment
@ContributesAndroidInjector abstract fun contributesTreatmentsTemporaryBasalsFragment(): TreatmentsTemporaryBasalsFragment @ContributesAndroidInjector abstract fun contributesTreatmentsTemporaryBasalsFragment(): TreatmentsTemporaryBasalsFragment
@ContributesAndroidInjector abstract fun contributesTreatmentsTempTargetFragment(): TreatmentsTempTargetFragment @ContributesAndroidInjector abstract fun contributesTreatmentsTempTargetFragment(): TreatmentsTempTargetFragment
@ -85,8 +79,7 @@ abstract class FragmentsModule {
@ContributesAndroidInjector abstract fun contributesEditEventDialog(): EditEventDialog @ContributesAndroidInjector abstract fun contributesEditEventDialog(): EditEventDialog
@ContributesAndroidInjector abstract fun contributesEditTriggerDialog(): EditTriggerDialog @ContributesAndroidInjector abstract fun contributesEditTriggerDialog(): EditTriggerDialog
@ContributesAndroidInjector @ContributesAndroidInjector abstract fun contributesEditQuickWizardDialog(): EditQuickWizardDialog
abstract fun contributesEditQuickWizardDialog(): EditQuickWizardDialog
@ContributesAndroidInjector abstract fun contributesExtendedBolusDialog(): ExtendedBolusDialog @ContributesAndroidInjector abstract fun contributesExtendedBolusDialog(): ExtendedBolusDialog
@ContributesAndroidInjector abstract fun contributesFillDialog(): FillDialog @ContributesAndroidInjector abstract fun contributesFillDialog(): FillDialog
@ -102,8 +95,7 @@ abstract class FragmentsModule {
@ContributesAndroidInjector abstract fun contributesWizardDialog(): WizardDialog @ContributesAndroidInjector abstract fun contributesWizardDialog(): WizardDialog
@ContributesAndroidInjector abstract fun contributesWizardInfoDialog(): WizardInfoDialog @ContributesAndroidInjector abstract fun contributesWizardInfoDialog(): WizardInfoDialog
@ContributesAndroidInjector @ContributesAndroidInjector abstract fun contributesExchangeAuthTokenDialog(): OpenHumansLoginActivity.ExchangeAuthTokenDialog
abstract fun contributesExchangeAuthTokenDialot(): OpenHumansLoginActivity.ExchangeAuthTokenDialog
@ContributesAndroidInjector abstract fun contributesPasswordCheck(): PasswordCheck @ContributesAndroidInjector abstract fun contributesPasswordCheck(): PasswordCheck
} }

View file

@ -37,7 +37,6 @@ import info.nightscout.androidaps.plugins.insulin.InsulinOrefRapidActingPlugin
import info.nightscout.androidaps.plugins.insulin.InsulinOrefUltraRapidActingPlugin import info.nightscout.androidaps.plugins.insulin.InsulinOrefUltraRapidActingPlugin
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin
import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin
import info.nightscout.androidaps.plugins.pump.mdi.MDIPlugin import info.nightscout.androidaps.plugins.pump.mdi.MDIPlugin
@ -151,11 +150,11 @@ abstract class PluginsModule {
@IntKey(140) @IntKey(140)
abstract fun bindComboPlugin(plugin: ComboPlugin): PluginBase abstract fun bindComboPlugin(plugin: ComboPlugin): PluginBase
@Binds // @Binds
@PumpDriver // @PumpDriver
@IntoMap // @IntoMap
@IntKey(150) // @IntKey(150)
abstract fun bindMedtronicPumpPlugin(plugin: MedtronicPumpPlugin): PluginBase // abstract fun bindMedtronicPumpPlugin(plugin: MedtronicPumpPlugin): PluginBase
@Binds @Binds
@PumpDriver @PumpDriver
@ -202,12 +201,6 @@ abstract class PluginsModule {
@Binds @Binds
@AllConfigs @AllConfigs
@IntoMap @IntoMap
@IntKey(230)
abstract fun bindNSProfilePlugin(plugin: NSProfilePlugin): PluginBase
@Binds
@NotNSClient
@IntoMap
@IntKey(240) @IntKey(240)
abstract fun bindLocalProfilePlugin(plugin: LocalProfilePlugin): PluginBase abstract fun bindLocalProfilePlugin(plugin: LocalProfilePlugin): PluginBase
@ -355,11 +348,11 @@ abstract class PluginsModule {
@IntKey(470) @IntKey(470)
abstract fun bindRandomBgPlugin(plugin: RandomBgPlugin): PluginBase abstract fun bindRandomBgPlugin(plugin: RandomBgPlugin): PluginBase
@Binds // @Binds
@NotNSClient // @NotNSClient
@IntoMap // @IntoMap
@IntKey(480) // @IntKey(480)
abstract fun bindOpenHumansPlugin(plugin: OpenHumansUploader): PluginBase // abstract fun bindOpenHumansPlugin(plugin: OpenHumansUploader): PluginBase
@Binds @Binds
@AllConfigs @AllConfigs

View file

@ -9,7 +9,7 @@ import info.nightscout.androidaps.plugins.general.nsclient.NSClientMbgWorker
import info.nightscout.androidaps.plugins.general.nsclient.NSClientRemoveWorker import info.nightscout.androidaps.plugins.general.nsclient.NSClientRemoveWorker
import info.nightscout.androidaps.plugins.general.nsclient.NSClientUpdateRemoveAckWorker import info.nightscout.androidaps.plugins.general.nsclient.NSClientUpdateRemoveAckWorker
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.source.* import info.nightscout.androidaps.plugins.source.*
@Module @Module
@ -24,7 +24,7 @@ abstract class WorkersModule {
@ContributesAndroidInjector abstract fun contributesTomatoWorker(): TomatoPlugin.TomatoWorker @ContributesAndroidInjector abstract fun contributesTomatoWorker(): TomatoPlugin.TomatoWorker
@ContributesAndroidInjector abstract fun contributesEversenseWorker(): EversensePlugin.EversenseWorker @ContributesAndroidInjector abstract fun contributesEversenseWorker(): EversensePlugin.EversenseWorker
@ContributesAndroidInjector abstract fun contributesNSClientSourceWorker(): NSClientSourcePlugin.NSClientSourceWorker @ContributesAndroidInjector abstract fun contributesNSClientSourceWorker(): NSClientSourcePlugin.NSClientSourceWorker
@ContributesAndroidInjector abstract fun contributesNSProfileWorker(): NSProfilePlugin.NSProfileWorker @ContributesAndroidInjector abstract fun contributesNSProfileWorker(): LocalProfilePlugin.NSProfileWorker
@ContributesAndroidInjector abstract fun contributesSmsCommunicatorWorker(): SmsCommunicatorPlugin.SmsCommunicatorWorker @ContributesAndroidInjector abstract fun contributesSmsCommunicatorWorker(): SmsCommunicatorPlugin.SmsCommunicatorWorker
@ContributesAndroidInjector abstract fun contributesNSClientWorker(): NSClientAddUpdateWorker @ContributesAndroidInjector abstract fun contributesNSClientWorker(): NSClientAddUpdateWorker
@ContributesAndroidInjector abstract fun contributesNSClientAddAckWorker(): NSClientAddAckWorker @ContributesAndroidInjector abstract fun contributesNSClientAddAckWorker(): NSClientAddAckWorker

View file

@ -17,7 +17,7 @@ import info.nightscout.androidaps.database.entities.TemporaryTarget
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.transactions.InsertTemporaryTargetAndCancelCurrentTransaction import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentTemporaryTargetTransaction
import info.nightscout.androidaps.databinding.DialogCarbsBinding import info.nightscout.androidaps.databinding.DialogCarbsBinding
import info.nightscout.androidaps.extensions.formatColor import info.nightscout.androidaps.extensions.formatColor
import info.nightscout.androidaps.interfaces.Constraint import info.nightscout.androidaps.interfaces.Constraint
@ -27,7 +27,6 @@ import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker 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.Callback
import info.nightscout.androidaps.queue.CommandQueue import info.nightscout.androidaps.queue.CommandQueue
import info.nightscout.androidaps.utils.* import info.nightscout.androidaps.utils.*
@ -47,7 +46,6 @@ class CarbsDialog : DialogFragmentWithDate() {
@Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var constraintChecker: ConstraintChecker @Inject lateinit var constraintChecker: ConstraintChecker
@Inject lateinit var defaultValueHelper: DefaultValueHelper @Inject lateinit var defaultValueHelper: DefaultValueHelper
@Inject lateinit var treatmentsPlugin: TreatmentsPlugin
@Inject lateinit var profileFunction: ProfileFunction @Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var iobCobCalculator: IobCobCalculator @Inject lateinit var iobCobCalculator: IobCobCalculator
@Inject lateinit var uel: UserEntryLogger @Inject lateinit var uel: UserEntryLogger
@ -228,7 +226,7 @@ class CarbsDialog : DialogFragmentWithDate() {
ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.ACTIVITY), ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.ACTIVITY),
ValueWithUnit.fromGlucoseUnit(activityTT, units.asText), ValueWithUnit.fromGlucoseUnit(activityTT, units.asText),
ValueWithUnit.Minute(activityTTDuration)) ValueWithUnit.Minute(activityTTDuration))
disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction( disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = System.currentTimeMillis(), timestamp = System.currentTimeMillis(),
duration = TimeUnit.MINUTES.toMillis(activityTTDuration.toLong()), duration = TimeUnit.MINUTES.toMillis(activityTTDuration.toLong()),
reason = TemporaryTarget.Reason.ACTIVITY, reason = TemporaryTarget.Reason.ACTIVITY,
@ -247,7 +245,7 @@ class CarbsDialog : DialogFragmentWithDate() {
ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.EATING_SOON), ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.EATING_SOON),
ValueWithUnit.fromGlucoseUnit(eatingSoonTT, units.asText), ValueWithUnit.fromGlucoseUnit(eatingSoonTT, units.asText),
ValueWithUnit.Minute(eatingSoonTTDuration)) ValueWithUnit.Minute(eatingSoonTTDuration))
disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction( disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = System.currentTimeMillis(), timestamp = System.currentTimeMillis(),
duration = TimeUnit.MINUTES.toMillis(eatingSoonTTDuration.toLong()), duration = TimeUnit.MINUTES.toMillis(eatingSoonTTDuration.toLong()),
reason = TemporaryTarget.Reason.EATING_SOON, reason = TemporaryTarget.Reason.EATING_SOON,
@ -266,7 +264,7 @@ class CarbsDialog : DialogFragmentWithDate() {
ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.HYPOGLYCEMIA), ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.HYPOGLYCEMIA),
ValueWithUnit.fromGlucoseUnit(hypoTT, units.asText), ValueWithUnit.fromGlucoseUnit(hypoTT, units.asText),
ValueWithUnit.Minute(hypoTTDuration)) ValueWithUnit.Minute(hypoTTDuration))
disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction( disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = System.currentTimeMillis(), timestamp = System.currentTimeMillis(),
duration = TimeUnit.MINUTES.toMillis(hypoTTDuration.toLong()), duration = TimeUnit.MINUTES.toMillis(hypoTTDuration.toLong()),
reason = TemporaryTarget.Reason.HYPOGLYCEMIA, reason = TemporaryTarget.Reason.HYPOGLYCEMIA,

View file

@ -16,7 +16,7 @@ import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.entities.TemporaryTarget import info.nightscout.androidaps.database.entities.TemporaryTarget
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.transactions.InsertTemporaryTargetAndCancelCurrentTransaction import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentTemporaryTargetTransaction
import info.nightscout.androidaps.databinding.DialogInsulinBinding import info.nightscout.androidaps.databinding.DialogInsulinBinding
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.logging.UserEntryLogger
@ -186,7 +186,7 @@ class InsulinDialog : DialogFragmentWithDate() {
ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.EATING_SOON), ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.EATING_SOON),
ValueWithUnit.fromGlucoseUnit(eatingSoonTT, units.asText), ValueWithUnit.fromGlucoseUnit(eatingSoonTT, units.asText),
ValueWithUnit.Minute(eatingSoonTTDuration)) ValueWithUnit.Minute(eatingSoonTTDuration))
disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction( disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = System.currentTimeMillis(), timestamp = System.currentTimeMillis(),
duration = TimeUnit.MINUTES.toMillis(eatingSoonTTDuration.toLong()), duration = TimeUnit.MINUTES.toMillis(eatingSoonTTDuration.toLong()),
reason = TemporaryTarget.Reason.EATING_SOON, reason = TemporaryTarget.Reason.EATING_SOON,

View file

@ -12,9 +12,13 @@ import androidx.fragment.app.FragmentManager
import dagger.android.support.DaggerDialogFragment import dagger.android.support.DaggerDialogFragment
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.ErrorHelperActivity import info.nightscout.androidaps.activities.ErrorHelperActivity
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.OfflineEvent
import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.transactions.CancelCurrentOfflineEventIfAnyTransaction
import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentOfflineEventTransaction
import info.nightscout.androidaps.databinding.DialogLoopBinding import info.nightscout.androidaps.databinding.DialogLoopBinding
import info.nightscout.androidaps.events.EventPreferenceChange import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.events.EventRefreshOverview import info.nightscout.androidaps.events.EventRefreshOverview
@ -29,8 +33,13 @@ import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import javax.inject.Inject import javax.inject.Inject
class LoopDialog : DaggerDialogFragment() { class LoopDialog : DaggerDialogFragment() {
@ -48,6 +57,8 @@ class LoopDialog : DaggerDialogFragment() {
@Inject lateinit var commandQueue: CommandQueueProvider @Inject lateinit var commandQueue: CommandQueueProvider
@Inject lateinit var configBuilder: ConfigBuilder @Inject lateinit var configBuilder: ConfigBuilder
@Inject lateinit var uel: UserEntryLogger @Inject lateinit var uel: UserEntryLogger
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var repository: AppRepository
private var showOkCancel: Boolean = true private var showOkCancel: Boolean = true
private var _binding: DialogLoopBinding? = null private var _binding: DialogLoopBinding? = null
@ -58,6 +69,8 @@ class LoopDialog : DaggerDialogFragment() {
// onDestroyView. // onDestroyView.
private val binding get() = _binding!! private val binding get() = _binding!!
val disposable = CompositeDisposable()
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
@ -118,6 +131,7 @@ class LoopDialog : DaggerDialogFragment() {
super.onDestroyView() super.onDestroyView()
_binding = null _binding = null
loopHandler.removeCallbacksAndMessages(null) loopHandler.removeCallbacksAndMessages(null)
disposable.clear()
} }
var task: Runnable? = null var task: Runnable? = null
@ -196,6 +210,7 @@ class LoopDialog : DaggerDialogFragment() {
binding.overviewDisconnectButtons.visibility = View.GONE binding.overviewDisconnectButtons.visibility = View.GONE
binding.overviewReconnect.visibility = View.VISIBLE binding.overviewReconnect.visibility = View.VISIBLE
} }
binding.overviewLoop.visibility = (!loopPlugin.isSuspended && !loopPlugin.isDisconnected).toVisibility()
} }
val profile = profileFunction.getProfile() val profile = profileFunction.getProfile()
val profileStore = activePlugin.activeProfileSource.profile val profileStore = activePlugin.activeProfileSource.profile
@ -237,7 +252,6 @@ class LoopDialog : DaggerDialogFragment() {
} }
fun onClick(v: View): Boolean { fun onClick(v: View): Boolean {
val profile = profileFunction.getProfile() ?: return true
when (v.id) { when (v.id) {
R.id.overview_closeloop -> { R.id.overview_closeloop -> {
uel.log(Action.CLOSED_LOOP_MODE, Sources.LoopDialog) uel.log(Action.CLOSED_LOOP_MODE, Sources.LoopDialog)
@ -273,7 +287,13 @@ class LoopDialog : DaggerDialogFragment() {
} }
} }
}) })
loopPlugin.createOfflineEvent(24 * 60) // upload 24h, we don't know real duration disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.days(365).msecs(), OfflineEvent.Reason.DISABLE_LOOP))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
return true return true
} }
@ -283,13 +303,23 @@ class LoopDialog : DaggerDialogFragment() {
loopPlugin.setFragmentVisible(PluginType.LOOP, true) loopPlugin.setFragmentVisible(PluginType.LOOP, true)
configBuilder.storeSettings("EnablingLoop") configBuilder.storeSettings("EnablingLoop")
rxBus.send(EventRefreshOverview("suspendmenu")) rxBus.send(EventRefreshOverview("suspendmenu"))
loopPlugin.createOfflineEvent(0) disposable += repository.runTransactionForResult(CancelCurrentOfflineEventIfAnyTransaction(dateUtil.now()))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
return true return true
} }
R.id.overview_resume, R.id.overview_reconnect -> { R.id.overview_resume, R.id.overview_reconnect -> {
uel.log(if (v.id==R.id.overview_resume) Action.RESUME else Action.RECONNECT, Sources.LoopDialog) uel.log(if (v.id == R.id.overview_resume) Action.RESUME else Action.RECONNECT, Sources.LoopDialog)
loopPlugin.suspendTo(0L) disposable += repository.runTransactionForResult(CancelCurrentOfflineEventIfAnyTransaction(dateUtil.now()))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
rxBus.send(EventRefreshOverview("suspendmenu")) rxBus.send(EventRefreshOverview("suspendmenu"))
commandQueue.cancelTempBasal(true, object : Callback() { commandQueue.cancelTempBasal(true, object : Callback() {
override fun run() { override fun run() {
@ -299,55 +329,96 @@ class LoopDialog : DaggerDialogFragment() {
} }
}) })
sp.putBoolean(R.string.key_objectiveusereconnect, true) sp.putBoolean(R.string.key_objectiveusereconnect, true)
loopPlugin.createOfflineEvent(0)
return true return true
} }
R.id.overview_suspend_1h -> { R.id.overview_suspend_1h -> {
uel.log(Action.SUSPEND, Sources.LoopDialog, ValueWithUnit.Hour(1)) uel.log(Action.SUSPEND, Sources.LoopDialog, ValueWithUnit.Hour(1))
loopPlugin.suspendLoop(60) disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.hours(1).msecs(), OfflineEvent.Reason.SUSPEND))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
rxBus.send(EventRefreshOverview("suspendmenu")) rxBus.send(EventRefreshOverview("suspendmenu"))
return true return true
} }
R.id.overview_suspend_2h -> { R.id.overview_suspend_2h -> {
uel.log(Action.SUSPEND, Sources.LoopDialog, ValueWithUnit.Hour(2)) uel.log(Action.SUSPEND, Sources.LoopDialog, ValueWithUnit.Hour(2))
loopPlugin.suspendLoop(120) disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.hours(2).msecs(), OfflineEvent.Reason.SUSPEND))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
rxBus.send(EventRefreshOverview("suspendmenu")) rxBus.send(EventRefreshOverview("suspendmenu"))
return true return true
} }
R.id.overview_suspend_3h -> { R.id.overview_suspend_3h -> {
uel.log(Action.SUSPEND, Sources.LoopDialog, ValueWithUnit.Hour(3)) uel.log(Action.SUSPEND, Sources.LoopDialog, ValueWithUnit.Hour(3))
loopPlugin.suspendLoop(180) disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.hours(3).msecs(), OfflineEvent.Reason.SUSPEND))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
rxBus.send(EventRefreshOverview("suspendmenu")) rxBus.send(EventRefreshOverview("suspendmenu"))
return true return true
} }
R.id.overview_suspend_10h -> { R.id.overview_suspend_10h -> {
uel.log(Action.SUSPEND, Sources.LoopDialog, ValueWithUnit.Hour(10)) uel.log(Action.SUSPEND, Sources.LoopDialog, ValueWithUnit.Hour(10))
loopPlugin.suspendLoop(600) disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.hours(10).msecs(), OfflineEvent.Reason.SUSPEND))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
rxBus.send(EventRefreshOverview("suspendmenu")) rxBus.send(EventRefreshOverview("suspendmenu"))
return true return true
} }
R.id.overview_disconnect_15m -> { R.id.overview_disconnect_15m -> {
uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Minute(15)) uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Minute(15))
loopPlugin.disconnectPump(15, profile) disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(15).msecs(), OfflineEvent.Reason.DISCONNECT_PUMP))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
rxBus.send(EventRefreshOverview("suspendmenu")) rxBus.send(EventRefreshOverview("suspendmenu"))
return true return true
} }
R.id.overview_disconnect_30m -> { R.id.overview_disconnect_30m -> {
uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Minute(30)) uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Minute(30))
loopPlugin.disconnectPump(30, profile) disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(30).msecs(), OfflineEvent.Reason.DISCONNECT_PUMP))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
rxBus.send(EventRefreshOverview("suspendmenu")) rxBus.send(EventRefreshOverview("suspendmenu"))
return true return true
} }
R.id.overview_disconnect_1h -> { R.id.overview_disconnect_1h -> {
uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Hour(1)) uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Hour(1))
loopPlugin.disconnectPump(60, profile) disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(60).msecs(), OfflineEvent.Reason.DISCONNECT_PUMP))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
sp.putBoolean(R.string.key_objectiveusedisconnect, true) sp.putBoolean(R.string.key_objectiveusedisconnect, true)
rxBus.send(EventRefreshOverview("suspendmenu")) rxBus.send(EventRefreshOverview("suspendmenu"))
return true return true
@ -355,14 +426,26 @@ class LoopDialog : DaggerDialogFragment() {
R.id.overview_disconnect_2h -> { R.id.overview_disconnect_2h -> {
uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Hour(2)) uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Hour(2))
loopPlugin.disconnectPump(120, profile) disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(120).msecs(), OfflineEvent.Reason.DISCONNECT_PUMP))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
rxBus.send(EventRefreshOverview("suspendmenu")) rxBus.send(EventRefreshOverview("suspendmenu"))
return true return true
} }
R.id.overview_disconnect_3h -> { R.id.overview_disconnect_3h -> {
uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Hour(3)) uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Hour(3))
loopPlugin.disconnectPump(180, profile) disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(180).msecs(), OfflineEvent.Reason.DISCONNECT_PUMP))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
rxBus.send(EventRefreshOverview("suspendmenu")) rxBus.send(EventRefreshOverview("suspendmenu"))
return true return true
} }

View file

@ -17,7 +17,7 @@ import info.nightscout.androidaps.database.entities.TemporaryTarget
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.transactions.CancelCurrentTemporaryTargetIfAnyTransaction import info.nightscout.androidaps.database.transactions.CancelCurrentTemporaryTargetIfAnyTransaction
import info.nightscout.androidaps.database.transactions.InsertTemporaryTargetAndCancelCurrentTransaction import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentTemporaryTargetTransaction
import info.nightscout.androidaps.databinding.DialogTemptargetBinding import info.nightscout.androidaps.databinding.DialogTemptargetBinding
import info.nightscout.androidaps.interfaces.GlucoseUnit import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.interfaces.ProfileFunction
@ -196,7 +196,7 @@ class TempTargetDialog : DialogFragmentWithDate() {
aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it) aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it)
}) })
} else { } else {
disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction( disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = eventTime, timestamp = eventTime,
duration = TimeUnit.MINUTES.toMillis(duration.toLong()), duration = TimeUnit.MINUTES.toMillis(duration.toLong()),
reason = when (reason) { reason = when (reason) {

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, iobCobCalculatorPluginHistory)
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], iobCobCalculatorPluginHistory)
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

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

View file

@ -16,18 +16,19 @@ import info.nightscout.androidaps.data.DetailedBolusInfo
import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.data.PumpEnactResult import info.nightscout.androidaps.data.PumpEnactResult
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.TherapyEvent import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.OfflineEvent
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.transactions.InsertIfNewByTimestampTherapyEventTransaction import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentOfflineEventTransaction
import info.nightscout.androidaps.database.transactions.InsertTherapyEventAnnouncementTransaction import info.nightscout.androidaps.database.transactions.InsertTherapyEventAnnouncementTransaction
import info.nightscout.androidaps.events.EventAcceptOpenLoopChange import info.nightscout.androidaps.events.EventAcceptOpenLoopChange
import info.nightscout.androidaps.events.EventAutosensCalculationFinished import info.nightscout.androidaps.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.events.EventNewBG import info.nightscout.androidaps.events.EventNewBG
import info.nightscout.androidaps.events.EventTempTargetChange import info.nightscout.androidaps.events.EventTempTargetChange
import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.interfaces.LoopInterface.LastRun import info.nightscout.androidaps.interfaces.Loop.LastRun
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.logging.UserEntryLogger
@ -54,6 +55,7 @@ import info.nightscout.androidaps.extensions.buildDeviceStatus
import info.nightscout.androidaps.extensions.convertedToAbsolute import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.convertedToPercent import info.nightscout.androidaps.extensions.convertedToPercent
import info.nightscout.androidaps.extensions.plannedRemainingMinutes import info.nightscout.androidaps.extensions.plannedRemainingMinutes
import info.nightscout.androidaps.plugins.aps.events.EventLoopInvoked
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.sharedPreferences.SP
@ -95,7 +97,7 @@ open class LoopPlugin @Inject constructor(
.enableByDefault(config.APS) .enableByDefault(config.APS)
.description(R.string.description_loop), .description(R.string.description_loop),
aapsLogger, resourceHelper, injector aapsLogger, resourceHelper, injector
), LoopInterface { ), Loop {
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
private var lastBgTriggeredRun: Long = 0 private var lastBgTriggeredRun: Long = 0
@ -157,48 +159,19 @@ open class LoopPlugin @Inject constructor(
} }
} }
override fun suspendTo(endTime: Long) { override fun minutesToEndOfSuspend(): Int {
sp.putLong("loopSuspendedTill", endTime) val offlineEventWrapped = repository.getOfflineEventActiveAt(dateUtil.now()).blockingGet()
sp.putBoolean("isSuperBolus", false) return if (offlineEventWrapped is ValueWrapper.Existing) T.msecs(offlineEventWrapped.value.timestamp + offlineEventWrapped.value.duration - dateUtil.now()).mins().toInt()
sp.putBoolean("isDisconnected", false) else 0
} }
fun superBolusTo(endTime: Long) {
sp.putLong("loopSuspendedTill", endTime)
sp.putBoolean("isSuperBolus", true)
sp.putBoolean("isDisconnected", false)
}
private fun disconnectTo(endTime: Long) {
sp.putLong("loopSuspendedTill", endTime)
sp.putBoolean("isSuperBolus", false)
sp.putBoolean("isDisconnected", true)
}
fun minutesToEndOfSuspend(): Int {
val loopSuspendedTill = sp.getLong("loopSuspendedTill", 0L)
if (loopSuspendedTill == 0L) return 0
val now = System.currentTimeMillis()
val millisDiff = loopSuspendedTill - now
if (loopSuspendedTill <= now) { // time exceeded
suspendTo(0L)
return 0
}
return (millisDiff / 60.0 / 1000.0).toInt()
}
// time exceeded
override val isSuspended: Boolean override val isSuspended: Boolean
get() { get() = repository.getOfflineEventActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing
val loopSuspendedTill = sp.getLong("loopSuspendedTill", 0L)
if (loopSuspendedTill == 0L) return false override var enabled: Boolean
val now = System.currentTimeMillis() get() = isEnabled()
if (loopSuspendedTill <= now) { // time exceeded set(value) { setPluginEnabled(PluginType.LOOP, value)}
suspendTo(0L)
return false
}
return true
}
val isLGS: Boolean val isLGS: Boolean
get() { get() {
val closedLoopEnabled = constraintChecker.isClosedLoopAllowed() val closedLoopEnabled = constraintChecker.isClosedLoopAllowed()
@ -210,30 +183,16 @@ open class LoopPlugin @Inject constructor(
return isLGS return isLGS
} }
// time exceeded
val isSuperBolus: Boolean val isSuperBolus: Boolean
get() { get() {
val loopSuspendedTill = sp.getLong("loopSuspendedTill", 0L) val offlineEventWrapped = repository.getOfflineEventActiveAt(dateUtil.now()).blockingGet()
if (loopSuspendedTill == 0L) return false return offlineEventWrapped is ValueWrapper.Existing && offlineEventWrapped.value.reason == OfflineEvent.Reason.SUPER_BOLUS
val now = System.currentTimeMillis()
if (loopSuspendedTill <= now) { // time exceeded
suspendTo(0L)
return false
}
return sp.getBoolean("isSuperBolus", false)
} }
// time exceeded
val isDisconnected: Boolean val isDisconnected: Boolean
get() { get() {
val loopSuspendedTill = sp.getLong("loopSuspendedTill", 0L) val offlineEventWrapped = repository.getOfflineEventActiveAt(dateUtil.now()).blockingGet()
if (loopSuspendedTill == 0L) return false return offlineEventWrapped is ValueWrapper.Existing && offlineEventWrapped.value.reason == OfflineEvent.Reason.DISCONNECT_PUMP
val now = System.currentTimeMillis()
if (loopSuspendedTill <= now) { // time exceeded
suspendTo(0L)
return false
}
return sp.getBoolean("isDisconnected", false)
} }
@Suppress("SameParameterValue") @Suppress("SameParameterValue")
@ -286,7 +245,7 @@ open class LoopPlugin @Inject constructor(
if (apsResult == null) { if (apsResult == null) {
rxBus.send(EventLoopSetLastRunGui(resourceHelper.gs(R.string.noapsselected))) rxBus.send(EventLoopSetLastRunGui(resourceHelper.gs(R.string.noapsselected)))
return return
} } else rxBus.send(EventLoopInvoked())
// Prepare for pumps using % basals // Prepare for pumps using % basals
if (pump.pumpDescription.tempBasalStyle == PumpDescription.PERCENT && allowPercentage()) { if (pump.pumpDescription.tempBasalStyle == PumpDescription.PERCENT && allowPercentage()) {
@ -403,7 +362,7 @@ open class LoopPlugin @Inject constructor(
val waiting = PumpEnactResult(injector) val waiting = PumpEnactResult(injector)
waiting.queued = true waiting.queued = true
if (resultAfterConstraints.tempBasalRequested) lastRun.tbrSetByPump = waiting if (resultAfterConstraints.tempBasalRequested) lastRun.tbrSetByPump = waiting
if (resultAfterConstraints.bolusRequested) lastRun.smbSetByPump = waiting if (resultAfterConstraints.bolusRequested()) lastRun.smbSetByPump = waiting
rxBus.send(EventLoopUpdateGui()) rxBus.send(EventLoopUpdateGui())
fabricPrivacy.logCustom("APSRequest") fabricPrivacy.logCustom("APSRequest")
applyTBRRequest(resultAfterConstraints, profile, object : Callback() { applyTBRRequest(resultAfterConstraints, profile, object : Callback() {
@ -600,7 +559,7 @@ open class LoopPlugin @Inject constructor(
} }
private fun applySMBRequest(request: APSResult, callback: Callback?) { private fun applySMBRequest(request: APSResult, callback: Callback?) {
if (!request.bolusRequested) { if (!request.bolusRequested()) {
return return
} }
val pump = activePlugin.activePump val pump = activePlugin.activePump
@ -641,11 +600,17 @@ open class LoopPlugin @Inject constructor(
return virtualPumpPlugin.isEnabled(PluginType.PUMP) return virtualPumpPlugin.isEnabled(PluginType.PUMP)
} }
fun disconnectPump(durationInMinutes: Int, profile: Profile?) { override fun goToZeroTemp(durationInMinutes: Int, profile: Profile, reason: OfflineEvent.Reason) {
val pump = activePlugin.activePump val pump = activePlugin.activePump
disconnectTo(System.currentTimeMillis() + durationInMinutes * 60 * 1000L) disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(durationInMinutes.toLong()).msecs(), reason))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
if (pump.pumpDescription.tempBasalStyle == PumpDescription.ABSOLUTE) { if (pump.pumpDescription.tempBasalStyle == PumpDescription.ABSOLUTE) {
commandQueue.tempBasalAbsolute(0.0, durationInMinutes, true, profile!!, PumpSync.TemporaryBasalType.EMULATED_PUMP_SUSPEND, object : Callback() { commandQueue.tempBasalAbsolute(0.0, durationInMinutes, true, profile, PumpSync.TemporaryBasalType.EMULATED_PUMP_SUSPEND, object : Callback() {
override fun run() { override fun run() {
if (!result.success) { if (!result.success) {
ErrorHelperActivity.runAlarm(context, result.comment, resourceHelper.gs(R.string.tempbasaldeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror) ErrorHelperActivity.runAlarm(context, result.comment, resourceHelper.gs(R.string.tempbasaldeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror)
@ -653,7 +618,7 @@ open class LoopPlugin @Inject constructor(
} }
}) })
} else { } else {
commandQueue.tempBasalPercent(0, durationInMinutes, true, profile!!, PumpSync.TemporaryBasalType.EMULATED_PUMP_SUSPEND, object : Callback() { commandQueue.tempBasalPercent(0, durationInMinutes, true, profile, PumpSync.TemporaryBasalType.EMULATED_PUMP_SUSPEND, object : Callback() {
override fun run() { override fun run() {
if (!result.success) { if (!result.success) {
ErrorHelperActivity.runAlarm(context, result.comment, resourceHelper.gs(R.string.tempbasaldeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror) ErrorHelperActivity.runAlarm(context, result.comment, resourceHelper.gs(R.string.tempbasaldeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror)
@ -670,11 +635,16 @@ open class LoopPlugin @Inject constructor(
} }
}) })
} }
createOfflineEvent(durationInMinutes)
} }
override fun suspendLoop(durationInMinutes: Int) { override fun suspendLoop(durationInMinutes: Int) {
suspendTo(System.currentTimeMillis() + durationInMinutes * 60 * 1000) disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(durationInMinutes.toLong()).msecs(), OfflineEvent.Reason.SUSPEND))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
commandQueue.cancelTempBasal(true, object : Callback() { commandQueue.cancelTempBasal(true, object : Callback() {
override fun run() { override fun run() {
if (!result.success) { if (!result.success) {
@ -682,20 +652,6 @@ open class LoopPlugin @Inject constructor(
} }
} }
}) })
createOfflineEvent(durationInMinutes)
}
override fun createOfflineEvent(durationInMinutes: Int) {
disposable += repository.runTransactionForResult(InsertIfNewByTimestampTherapyEventTransaction(
timestamp = dateUtil.now(),
type = TherapyEvent.Type.APS_OFFLINE,
duration = T.mins(durationInMinutes.toLong()).msecs(),
enteredBy = "openaps://" + "AndroidAPS",
glucoseUnit = TherapyEvent.GlucoseUnit.MGDL
)).subscribe(
{ result -> result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted therapy event $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while saving therapy event", it) }
)
} }
companion object { companion object {

View file

@ -40,7 +40,6 @@ class DetermineBasalResultAMA private constructor(injector: HasAndroidInjector)
tempBasalRequested = false tempBasalRequested = false
} }
} }
bolusRequested = false
} }
override fun newAndClone(injector: HasAndroidInjector): DetermineBasalResultAMA { override fun newAndClone(injector: HasAndroidInjector): DetermineBasalResultAMA {

View file

@ -58,6 +58,7 @@ class OpenAPSAMAFragment : DaggerFragment() {
} }
@Synchronized @Synchronized
@kotlin.ExperimentalStdlibApi
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@ -90,6 +91,7 @@ class OpenAPSAMAFragment : DaggerFragment() {
} }
@Synchronized @Synchronized
@kotlin.ExperimentalStdlibApi
private fun updateGUI() { private fun updateGUI() {
if (_binding == null) return if (_binding == null) return
openAPSAMAPlugin.lastAPSResult?.let { lastAPSResult -> openAPSAMAPlugin.lastAPSResult?.let { lastAPSResult ->

View file

@ -35,7 +35,6 @@ class DetermineBasalResultSMB private constructor(injector: HasAndroidInjector)
duration = -1 duration = -1
} }
if (result.has("units")) { if (result.has("units")) {
bolusRequested = true
smb = result.getDouble("units") smb = result.getDouble("units")
} else { } else {
smb = 0.0 smb = 0.0

View file

@ -59,6 +59,7 @@ class OpenAPSSMBFragment : DaggerFragment() {
} }
@Synchronized @Synchronized
@kotlin.ExperimentalStdlibApi
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
disposable += rxBus disposable += rxBus
@ -90,6 +91,7 @@ class OpenAPSSMBFragment : DaggerFragment() {
} }
@Synchronized @Synchronized
@kotlin.ExperimentalStdlibApi
fun updateGUI() { fun updateGUI() {
if (_binding == null) return if (_binding == null) return
openAPSSMBPlugin.lastAPSResult?.let { lastAPSResult -> openAPSSMBPlugin.lastAPSResult?.let { lastAPSResult ->

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.plugins.configBuilder package info.nightscout.androidaps.plugins.configBuilder
import androidx.collection.LongSparseArray
import info.nightscout.androidaps.Constants import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.core.R import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.data.ProfileSealed import info.nightscout.androidaps.data.ProfileSealed
@ -35,6 +36,8 @@ class ProfileFunctionImplementation @Inject constructor(
private val dateUtil: DateUtil private val dateUtil: DateUtil
) : ProfileFunction { ) : ProfileFunction {
val cache = LongSparseArray<Profile>()
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
override fun getProfileName(): String = override fun getProfileName(): String =
@ -65,10 +68,20 @@ class ProfileFunctionImplementation @Inject constructor(
getProfile(dateUtil.now()) getProfile(dateUtil.now())
override fun getProfile(time: Long): Profile? { override fun getProfile(time: Long): Profile? {
// aapsLogger.debug("XXXXXXXXXXXXXXX getProfile called for $time") val rounded = time - time % 1000
val cached = cache[rounded]
if (cached != null) {
// aapsLogger.debug("XXXXXXXXXXXXXXX HIT getProfile for $time $rounded")
return cached
}
// aapsLogger.debug("XXXXXXXXXXXXXXX getProfile called for $time")
val ps = repository.getEffectiveProfileSwitchActiveAt(time).blockingGet() val ps = repository.getEffectiveProfileSwitchActiveAt(time).blockingGet()
return if (ps is ValueWrapper.Existing) ProfileSealed.EPS(ps.value) if (ps is ValueWrapper.Existing) {
else null val sealed = ProfileSealed.EPS(ps.value)
cache.put(rounded, sealed)
return sealed
}
return null
} }
override fun getRequestedProfile(): ProfileSwitch? = repository.getActiveProfileSwitch(dateUtil.now()) override fun getRequestedProfile(): ProfileSwitch? = repository.getActiveProfileSwitch(dateUtil.now())
@ -91,7 +104,8 @@ class ProfileFunctionImplementation @Inject constructor(
timeshift = T.hours(timeShiftInHours.toLong()).msecs(), timeshift = T.hours(timeShiftInHours.toLong()).msecs(),
percentage = percentage, percentage = percentage,
duration = T.mins(durationInMinutes.toLong()).msecs(), duration = T.mins(durationInMinutes.toLong()).msecs(),
insulinConfiguration = activePlugin.activeInsulin.insulinConfiguration) insulinConfiguration = activePlugin.activeInsulin.insulinConfiguration.also { it.insulinEndTime = (pureProfile.dia * 3600 * 1000).toLong() }
)
disposable += repository.runTransactionForResult(InsertOrUpdateProfileSwitch(ps)) disposable += repository.runTransactionForResult(InsertOrUpdateProfileSwitch(ps))
.subscribe({ result -> .subscribe({ result ->
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted ProfileSwitch $it") } result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted ProfileSwitch $it") }

View file

@ -120,9 +120,10 @@ class ObjectivesPlugin @Inject constructor(
sp.putBoolean(R.string.key_objectiveusescale, false) sp.putBoolean(R.string.key_objectiveusescale, false)
} }
@kotlin.ExperimentalStdlibApi
fun completeObjectives(activity: FragmentActivity, request: String) { fun completeObjectives(activity: FragmentActivity, request: String) {
val requestCode = sp.getString(R.string.key_objectives_request_code, "") val requestCode = sp.getString(R.string.key_objectives_request_code, "")
var url = sp.getString(R.string.key_nsclientinternal_url, "").toLowerCase(Locale.getDefault()) var url = sp.getString(R.string.key_nsclientinternal_url, "").lowercase(Locale.getDefault())
if (!url.endsWith("/")) url = "$url/" if (!url.endsWith("/")) url = "$url/"
@Suppress("DEPRECATION") val hashNS = Hashing.sha1().hashString(url + BuildConfig.APPLICATION_ID + "/" + requestCode, Charsets.UTF_8).toString() @Suppress("DEPRECATION") val hashNS = Hashing.sha1().hashString(url + BuildConfig.APPLICATION_ID + "/" + requestCode, Charsets.UTF_8).toString()
if (request.equals(hashNS.substring(0, 10), ignoreCase = true)) { if (request.equals(hashNS.substring(0, 10), ignoreCase = true)) {

View file

@ -5,7 +5,6 @@ import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin
import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin
import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientService
import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.T
import javax.inject.Inject import javax.inject.Inject
@ -28,8 +27,9 @@ class Objective3 @Inject constructor(injector: HasAndroidInjector) : Objective(i
} }
override fun specialActionEnabled(): Boolean = override fun specialActionEnabled(): Boolean =
NSClientService.isConnected && NSClientService.hasWriteAuth nsClientPlugin.nsClientService?.isConnected == true && nsClientPlugin.nsClientService?.hasWriteAuth == true
@kotlin.ExperimentalStdlibApi
override fun specialAction(activity: FragmentActivity, input: String) { override fun specialAction(activity: FragmentActivity, input: String) {
objectivesPlugin.completeObjectives(activity, input) objectivesPlugin.completeObjectives(activity, input)
} }

View file

@ -152,12 +152,13 @@ class SignatureVerifierPlugin @Inject constructor(
return sb.toString() return sb.toString()
} }
@kotlin.ExperimentalStdlibApi
fun singleCharUnMap(shortHash: String): String { fun singleCharUnMap(shortHash: String): String {
val array = ByteArray(shortHash.length) val array = ByteArray(shortHash.length)
val sb = StringBuilder() val sb = StringBuilder()
for (i in array.indices) { for (i in array.indices) {
if (i != 0) sb.append(":") if (i != 0) sb.append(":")
sb.append(String.format("%02X", 0xFF and map[map.indexOf(shortHash[i])].toInt())) sb.append(String.format("%02X", 0xFF and map[map.indexOf(shortHash[i])].code))
} }
return sb.toString() return sb.toString()
} }

View file

@ -11,7 +11,6 @@ import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.Constants import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.ErrorHelperActivity import info.nightscout.androidaps.activities.ErrorHelperActivity
@ -21,13 +20,18 @@ import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.dialogs.* import info.nightscout.androidaps.dialogs.*
import info.nightscout.androidaps.events.* import info.nightscout.androidaps.events.EventCustomActionsChanged
import info.nightscout.androidaps.events.EventExtendedBolusChange
import info.nightscout.androidaps.events.EventInitializationChanged
import info.nightscout.androidaps.events.EventTempBasalChange
import info.nightscout.androidaps.events.EventTherapyEventChange
import info.nightscout.androidaps.extensions.toStringMedium import info.nightscout.androidaps.extensions.toStringMedium
import info.nightscout.androidaps.extensions.toStringShort import info.nightscout.androidaps.extensions.toStringShort
import info.nightscout.androidaps.extensions.toVisibility 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.ActivePlugin
import info.nightscout.androidaps.interfaces.CommandQueueProvider import info.nightscout.androidaps.interfaces.CommandQueueProvider
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.IobCobCalculator import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
@ -229,10 +233,6 @@ class ActionsFragment : DaggerFragment() {
.toObservable(EventInitializationChanged::class.java) .toObservable(EventInitializationChanged::class.java)
.observeOn(aapsSchedulers.main) .observeOn(aapsSchedulers.main)
.subscribe({ updateGui() }, fabricPrivacy::logException) .subscribe({ updateGui() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventRefreshOverview::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ updateGui() }, fabricPrivacy::logException)
disposable += rxBus disposable += rxBus
.toObservable(EventExtendedBolusChange::class.java) .toObservable(EventExtendedBolusChange::class.java)
.observeOn(aapsSchedulers.main) .observeOn(aapsSchedulers.main)

View file

@ -67,6 +67,7 @@ class FoodFragment : DaggerFragment() {
return binding.root return binding.root
} }
@kotlin.ExperimentalStdlibApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -128,6 +129,7 @@ class FoodFragment : DaggerFragment() {
} }
@Synchronized @Synchronized
@kotlin.ExperimentalStdlibApi
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
disposable.add(rxBus disposable.add(rxBus
@ -139,6 +141,7 @@ class FoodFragment : DaggerFragment() {
swapAdapter() swapAdapter()
} }
@kotlin.ExperimentalStdlibApi
private fun swapAdapter() { private fun swapAdapter() {
disposable += repository disposable += repository
.getFoodData() .getFoodData()
@ -199,6 +202,7 @@ class FoodFragment : DaggerFragment() {
} }
} }
@kotlin.ExperimentalStdlibApi
private fun filterData() { private fun filterData() {
val textFilter = binding.filter.text.toString() val textFilter = binding.filter.text.toString()
val categoryFilter = binding.category.selectedItem?.toString() val categoryFilter = binding.category.selectedItem?.toString()
@ -210,7 +214,7 @@ class FoodFragment : DaggerFragment() {
if (f.category == null || f.subCategory == null) continue if (f.category == null || f.subCategory == null) continue
if (subcategoryFilter != resourceHelper.gs(R.string.none) && f.subCategory != subcategoryFilter) continue if (subcategoryFilter != resourceHelper.gs(R.string.none) && f.subCategory != subcategoryFilter) continue
if (categoryFilter != resourceHelper.gs(R.string.none) && f.category != categoryFilter) continue if (categoryFilter != resourceHelper.gs(R.string.none) && f.category != categoryFilter) continue
if (textFilter != "" && !f.name.toLowerCase(Locale.getDefault()).contains(textFilter.toLowerCase(Locale.getDefault()))) continue if (textFilter != "" && !f.name.lowercase(Locale.getDefault()).contains(textFilter.lowercase(Locale.getDefault()))) continue
newFiltered.add(f) newFiltered.add(f)
} }
filtered = newFiltered filtered = newFiltered

View file

@ -7,13 +7,14 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.dana.database.DanaHistoryDatabase
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.databinding.MaintenanceFragmentBinding import info.nightscout.androidaps.databinding.MaintenanceFragmentBinding
import info.nightscout.androidaps.events.EventNewBG import info.nightscout.androidaps.events.EventNewBG
import info.nightscout.androidaps.insight.database.InsightDatabase
import info.nightscout.androidaps.interfaces.DataSyncSelector import info.nightscout.androidaps.interfaces.DataSyncSelector
import info.nightscout.androidaps.interfaces.DatabaseHelperInterface
import info.nightscout.androidaps.interfaces.ImportExportPrefs import info.nightscout.androidaps.interfaces.ImportExportPrefs
import info.nightscout.androidaps.interfaces.PumpSync import info.nightscout.androidaps.interfaces.PumpSync
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
@ -38,7 +39,8 @@ class MaintenanceFragment : DaggerFragment() {
@Inject lateinit var importExportPrefs: ImportExportPrefs @Inject lateinit var importExportPrefs: ImportExportPrefs
@Inject lateinit var aapsSchedulers: AapsSchedulers @Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var repository: AppRepository @Inject lateinit var repository: AppRepository
@Inject lateinit var databaseHelper: DatabaseHelperInterface @Inject lateinit var danaHistoryDatabase: DanaHistoryDatabase
@Inject lateinit var insightDatabase: InsightDatabase
@Inject lateinit var uel: UserEntryLogger @Inject lateinit var uel: UserEntryLogger
@Inject lateinit var dataSyncSelector: DataSyncSelector @Inject lateinit var dataSyncSelector: DataSyncSelector
@Inject lateinit var pumpSync: PumpSync @Inject lateinit var pumpSync: PumpSync
@ -68,8 +70,9 @@ class MaintenanceFragment : DaggerFragment() {
OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.maintenance), resourceHelper.gs(R.string.reset_db_confirm), Runnable { OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.maintenance), resourceHelper.gs(R.string.reset_db_confirm), Runnable {
compositeDisposable.add( compositeDisposable.add(
fromAction { fromAction {
databaseHelper.resetDatabases()
repository.clearDatabases() repository.clearDatabases()
danaHistoryDatabase.clearAllTables()
insightDatabase.clearAllTables()
dataSyncSelector.resetToNextFullSync() dataSyncSelector.resetToNextFullSync()
pumpSync.connectNewPump() pumpSync.connectNewPump()
} }

View file

@ -2,14 +2,15 @@ package info.nightscout.androidaps.plugins.general.nsclient
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.DeviceStatus import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.* import info.nightscout.androidaps.database.entities.*
import info.nightscout.androidaps.extensions.toJson
import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.DataSyncSelector import info.nightscout.androidaps.interfaces.DataSyncSelector
import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.extensions.toJson import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.sharedPreferences.SP
import javax.inject.Inject import javax.inject.Inject
@ -21,9 +22,28 @@ class DataSyncSelectorImplementation @Inject constructor(
private val profileFunction: ProfileFunction, private val profileFunction: ProfileFunction,
private val nsClientPlugin: NSClientPlugin, private val nsClientPlugin: NSClientPlugin,
private val activePlugin: ActivePlugin, private val activePlugin: ActivePlugin,
private val appRepository: AppRepository private val appRepository: AppRepository,
private val localProfilePlugin: LocalProfilePlugin
) : DataSyncSelector { ) : DataSyncSelector {
override fun doUpload() {
if (sp.getBoolean(R.string.key_ns_upload, true)) {
processChangedBolusesCompat()
processChangedCarbsCompat()
processChangedBolusCalculatorResultsCompat()
processChangedTemporaryBasalsCompat()
processChangedExtendedBolusesCompat()
processChangedProfileSwitchesCompat()
processChangedGlucoseValuesCompat()
processChangedTempTargetsCompat()
processChangedFoodsCompat()
processChangedTherapyEventsCompat()
processChangedDeviceStatusesCompat()
processChangedOfflineEventsCompat()
processChangedProfileStore()
}
}
override fun resetToNextFullSync() { override fun resetToNextFullSync() {
sp.remove(R.string.key_ns_temporary_target_last_synced_id) sp.remove(R.string.key_ns_temporary_target_last_synced_id)
sp.remove(R.string.key_ns_glucose_value_last_synced_id) sp.remove(R.string.key_ns_glucose_value_last_synced_id)
@ -36,6 +56,8 @@ class DataSyncSelectorImplementation @Inject constructor(
sp.remove(R.string.key_ns_extended_bolus_last_synced_id) sp.remove(R.string.key_ns_extended_bolus_last_synced_id)
sp.remove(R.string.key_ns_therapy_event_last_synced_id) sp.remove(R.string.key_ns_therapy_event_last_synced_id)
sp.remove(R.string.key_ns_profile_switch_last_synced_id) sp.remove(R.string.key_ns_profile_switch_last_synced_id)
sp.remove(R.string.key_ns_offline_event_last_synced_id)
sp.remove(R.string.key_ns_profile_store_last_synced_timestamp)
} }
override fun confirmLastBolusIdIfGreater(lastSynced: Long) { override fun confirmLastBolusIdIfGreater(lastSynced: Long) {
@ -56,22 +78,28 @@ class DataSyncSelectorImplementation @Inject constructor(
} }
} }
private var lastBolusId = -1L
private var lastBolusTime = -1L
override fun processChangedBolusesCompat(): Boolean { override fun processChangedBolusesCompat(): Boolean {
val startId = sp.getLong(R.string.key_ns_bolus_last_synced_id, 0) val lastDbIdWrapped = appRepository.getLastBolusIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
var startId = sp.getLong(R.string.key_ns_bolus_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_ns_bolus_last_synced_id, 0)
startId = 0
}
if (startId == lastBolusId && dateUtil.now() - lastBolusTime < 5000) return false
lastBolusId = startId
lastBolusTime = dateUtil.now()
appRepository.getNextSyncElementBolus(startId).blockingGet()?.let { bolus -> appRepository.getNextSyncElementBolus(startId).blockingGet()?.let { bolus ->
aapsLogger.info(LTag.DATABASE, "Loading Bolus data Start: $startId ID: ${bolus.first.id} HistoryID: ${bolus.second} ") aapsLogger.info(LTag.DATABASE, "Loading Bolus data Start: $startId ID: ${bolus.first.id} HistoryID: ${bolus.second} ")
when { when {
// removed and not uploaded yet = ignore // without nsId = create new
!bolus.first.isValid && bolus.first.interfaceIDs.nightscoutId == null -> Any() bolus.first.interfaceIDs.nightscoutId == null ->
// removed and already uploaded = send for removal nsClientPlugin.nsClientService?.dbAdd("treatments", bolus.first.toJson(true, dateUtil), DataSyncSelector.PairBolus(bolus.first, bolus.second), "$startId/$lastDbId")
!bolus.first.isValid && bolus.first.interfaceIDs.nightscoutId != null -> // with nsId = update
nsClientPlugin.nsClientService?.dbRemove("treatments", bolus.first.interfaceIDs.nightscoutId, DataSyncSelector.PairBolus(bolus.first, bolus.second)) bolus.first.interfaceIDs.nightscoutId != null ->
// existing without nsId = create new nsClientPlugin.nsClientService?.dbUpdate("treatments", bolus.first.interfaceIDs.nightscoutId, bolus.first.toJson(false, dateUtil), DataSyncSelector.PairBolus(bolus.first, bolus.second), "$startId/$lastDbId")
bolus.first.isValid && bolus.first.interfaceIDs.nightscoutId == null ->
nsClientPlugin.nsClientService?.dbAdd("treatments", bolus.first.toJson(dateUtil), DataSyncSelector.PairBolus(bolus.first, bolus.second))
// existing with nsId = update
bolus.first.isValid && bolus.first.interfaceIDs.nightscoutId != null ->
nsClientPlugin.nsClientService?.dbUpdate("treatments", bolus.first.interfaceIDs.nightscoutId, bolus.first.toJson(dateUtil), DataSyncSelector.PairBolus(bolus.first, bolus.second))
} }
return true return true
} }
@ -93,22 +121,28 @@ class DataSyncSelectorImplementation @Inject constructor(
} }
} }
private var lastCarbsId = -1L
private var lastCarbsTime = -1L
override fun processChangedCarbsCompat(): Boolean { override fun processChangedCarbsCompat(): Boolean {
val startId = sp.getLong(R.string.key_ns_carbs_last_synced_id, 0) val lastDbIdWrapped = appRepository.getLastCarbsIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
var startId = sp.getLong(R.string.key_ns_carbs_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_ns_carbs_last_synced_id, 0)
startId = 0
}
if (startId == lastCarbsId && dateUtil.now() - lastCarbsTime < 5000) return false
lastCarbsId = startId
lastCarbsTime = dateUtil.now()
appRepository.getNextSyncElementCarbs(startId).blockingGet()?.let { carb -> appRepository.getNextSyncElementCarbs(startId).blockingGet()?.let { carb ->
aapsLogger.info(LTag.DATABASE, "Loading Carbs data Start: $startId ID: ${carb.first.id} HistoryID: ${carb.second} ") aapsLogger.info(LTag.DATABASE, "Loading Carbs data Start: $startId ID: ${carb.first.id} HistoryID: ${carb.second} ")
when { when {
// removed and not uploaded yet = ignore // without nsId = create new
!carb.first.isValid && carb.first.interfaceIDs.nightscoutId == null -> Any() carb.first.interfaceIDs.nightscoutId == null ->
// removed and already uploaded = send for removal nsClientPlugin.nsClientService?.dbAdd("treatments", carb.first.toJson(true, dateUtil), DataSyncSelector.PairCarbs(carb.first, carb.second), "$startId/$lastDbId")
!carb.first.isValid && carb.first.interfaceIDs.nightscoutId != null -> // with nsId = update
nsClientPlugin.nsClientService?.dbRemove("treatments", carb.first.interfaceIDs.nightscoutId, DataSyncSelector.PairCarbs(carb.first, carb.second)) carb.first.interfaceIDs.nightscoutId != null ->
// existing without nsId = create new nsClientPlugin.nsClientService?.dbUpdate("treatments", carb.first.interfaceIDs.nightscoutId, carb.first.toJson(false, dateUtil), DataSyncSelector.PairCarbs(carb.first, carb.second), "$startId/$lastDbId")
carb.first.isValid && carb.first.interfaceIDs.nightscoutId == null ->
nsClientPlugin.nsClientService?.dbAdd("treatments", carb.first.toJson(dateUtil), DataSyncSelector.PairCarbs(carb.first, carb.second))
// existing with nsId = update
carb.first.isValid && carb.first.interfaceIDs.nightscoutId != null ->
nsClientPlugin.nsClientService?.dbUpdate("treatments", carb.first.interfaceIDs.nightscoutId, carb.first.toJson(dateUtil), DataSyncSelector.PairCarbs(carb.first, carb.second))
} }
return true return true
} }
@ -130,22 +164,28 @@ class DataSyncSelectorImplementation @Inject constructor(
} }
} }
private var lastBcrId = -1L
private var lastBcrTime = -1L
override fun processChangedBolusCalculatorResultsCompat(): Boolean { override fun processChangedBolusCalculatorResultsCompat(): Boolean {
val startId = sp.getLong(R.string.key_ns_bolus_calculator_result_last_synced_id, 0) val lastDbIdWrapped = appRepository.getLastBolusCalculatorResultIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
var startId = sp.getLong(R.string.key_ns_bolus_calculator_result_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_ns_bolus_calculator_result_last_synced_id, 0)
startId = 0
}
if (startId == lastBcrId && dateUtil.now() - lastBcrTime < 5000) return false
lastBcrId = startId
lastBcrTime = dateUtil.now()
appRepository.getNextSyncElementBolusCalculatorResult(startId).blockingGet()?.let { bolusCalculatorResult -> appRepository.getNextSyncElementBolusCalculatorResult(startId).blockingGet()?.let { bolusCalculatorResult ->
aapsLogger.info(LTag.DATABASE, "Loading BolusCalculatorResult data Start: $startId ID: ${bolusCalculatorResult.first.id} HistoryID: ${bolusCalculatorResult.second} ") aapsLogger.info(LTag.DATABASE, "Loading BolusCalculatorResult data Start: $startId ID: ${bolusCalculatorResult.first.id} HistoryID: ${bolusCalculatorResult.second} ")
when { when {
// removed and not uploaded yet = ignore // without nsId = create new
!bolusCalculatorResult.first.isValid && bolusCalculatorResult.first.interfaceIDs.nightscoutId == null -> Any() bolusCalculatorResult.first.interfaceIDs.nightscoutId == null ->
// removed and already uploaded = send for removal nsClientPlugin.nsClientService?.dbAdd("treatments", bolusCalculatorResult.first.toJson(true, dateUtil), DataSyncSelector.PairBolusCalculatorResult(bolusCalculatorResult.first, bolusCalculatorResult.second), "$startId/$lastDbId")
!bolusCalculatorResult.first.isValid && bolusCalculatorResult.first.interfaceIDs.nightscoutId != null -> // with nsId = update
nsClientPlugin.nsClientService?.dbRemove("treatments", bolusCalculatorResult.first.interfaceIDs.nightscoutId, DataSyncSelector.PairBolusCalculatorResult(bolusCalculatorResult.first, bolusCalculatorResult.second)) bolusCalculatorResult.first.interfaceIDs.nightscoutId != null ->
// existing without nsId = create new nsClientPlugin.nsClientService?.dbUpdate("treatments", bolusCalculatorResult.first.interfaceIDs.nightscoutId, bolusCalculatorResult.first.toJson(false, dateUtil), DataSyncSelector.PairBolusCalculatorResult(bolusCalculatorResult.first, bolusCalculatorResult.second), "$startId/$lastDbId")
bolusCalculatorResult.first.isValid && bolusCalculatorResult.first.interfaceIDs.nightscoutId == null ->
nsClientPlugin.nsClientService?.dbAdd("treatments", bolusCalculatorResult.first.toJson(dateUtil), DataSyncSelector.PairBolusCalculatorResult(bolusCalculatorResult.first, bolusCalculatorResult.second))
// existing with nsId = update
bolusCalculatorResult.first.isValid && bolusCalculatorResult.first.interfaceIDs.nightscoutId != null ->
nsClientPlugin.nsClientService?.dbUpdate("treatments", bolusCalculatorResult.first.interfaceIDs.nightscoutId, bolusCalculatorResult.first.toJson(dateUtil), DataSyncSelector.PairBolusCalculatorResult(bolusCalculatorResult.first, bolusCalculatorResult.second))
} }
return true return true
} }
@ -167,22 +207,28 @@ class DataSyncSelectorImplementation @Inject constructor(
} }
} }
private var lastTtId = -1L
private var lastTtTime = -1L
override fun processChangedTempTargetsCompat(): Boolean { override fun processChangedTempTargetsCompat(): Boolean {
val startId = sp.getLong(R.string.key_ns_temporary_target_last_synced_id, 0) val lastDbIdWrapped = appRepository.getLastTempTargetIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
var startId = sp.getLong(R.string.key_ns_temporary_target_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_ns_temporary_target_last_synced_id, 0)
startId = 0
}
if (startId == lastTtId && dateUtil.now() - lastTtTime < 5000) return false
lastTtId = startId
lastTtTime = dateUtil.now()
appRepository.getNextSyncElementTemporaryTarget(startId).blockingGet()?.let { tt -> appRepository.getNextSyncElementTemporaryTarget(startId).blockingGet()?.let { tt ->
aapsLogger.info(LTag.DATABASE, "Loading TemporaryTarget data Start: $startId ID: ${tt.first.id} HistoryID: ${tt.second} ") aapsLogger.info(LTag.DATABASE, "Loading TemporaryTarget data Start: $startId ID: ${tt.first.id} HistoryID: ${tt.second} ")
when { when {
// removed and not uploaded yet = ignore // without nsId = create new
!tt.first.isValid && tt.first.interfaceIDs.nightscoutId == null -> Any() tt.first.interfaceIDs.nightscoutId == null ->
// removed and already uploaded = send for removal nsClientPlugin.nsClientService?.dbAdd("treatments", tt.first.toJson(true, profileFunction.getUnits(), dateUtil), DataSyncSelector.PairTemporaryTarget(tt.first, tt.second), "$startId/$lastDbId")
!tt.first.isValid && tt.first.interfaceIDs.nightscoutId != null ->
nsClientPlugin.nsClientService?.dbRemove("treatments", tt.first.interfaceIDs.nightscoutId, DataSyncSelector.PairTemporaryTarget(tt.first, tt.second))
// existing without nsId = create new
tt.first.isValid && tt.first.interfaceIDs.nightscoutId == null ->
nsClientPlugin.nsClientService?.dbAdd("treatments", tt.first.toJson(profileFunction.getUnits(), dateUtil), DataSyncSelector.PairTemporaryTarget(tt.first, tt.second))
// existing with nsId = update // existing with nsId = update
tt.first.isValid && tt.first.interfaceIDs.nightscoutId != null -> tt.first.interfaceIDs.nightscoutId != null ->
nsClientPlugin.nsClientService?.dbUpdate("treatments", tt.first.interfaceIDs.nightscoutId, tt.first.toJson(profileFunction.getUnits(), dateUtil), DataSyncSelector.PairTemporaryTarget(tt.first, tt.second)) nsClientPlugin.nsClientService?.dbUpdate("treatments", tt.first.interfaceIDs.nightscoutId, tt.first.toJson(false, profileFunction.getUnits(), dateUtil), DataSyncSelector.PairTemporaryTarget(tt.first, tt.second), "$startId/$lastDbId")
} }
return true return true
} }
@ -204,22 +250,28 @@ class DataSyncSelectorImplementation @Inject constructor(
} }
} }
private var lastFoodId = -1L
private var lastFoodTime = -1L
override fun processChangedFoodsCompat(): Boolean { override fun processChangedFoodsCompat(): Boolean {
val startId = sp.getLong(R.string.key_ns_food_last_synced_id, 0) val lastDbIdWrapped = appRepository.getLastFoodIdWrapped().blockingGet()
appRepository.getNextSyncElementFood(startId).blockingGet()?.let { tt -> val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
aapsLogger.info(LTag.DATABASE, "Loading Food data Start: $startId ID: ${tt.first.id} HistoryID: ${tt.second} ") var startId = sp.getLong(R.string.key_ns_food_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_ns_food_last_synced_id, 0)
startId = 0
}
if (startId == lastFoodId && dateUtil.now() - lastFoodTime < 5000) return false
lastFoodId = startId
lastFoodTime = dateUtil.now()
appRepository.getNextSyncElementFood(startId).blockingGet()?.let { food ->
aapsLogger.info(LTag.DATABASE, "Loading Food data Start: $startId ID: ${food.first.id} HistoryID: ${food.second} ")
when { when {
// removed and not uploaded yet = ignore // without nsId = create new
!tt.first.isValid && tt.first.interfaceIDs.nightscoutId == null -> Any() food.first.interfaceIDs.nightscoutId == null ->
// removed and already uploaded = send for removal nsClientPlugin.nsClientService?.dbAdd("food", food.first.toJson(true), DataSyncSelector.PairFood(food.first, food.second), "$startId/$lastDbId")
!tt.first.isValid && tt.first.interfaceIDs.nightscoutId != null -> // with nsId = update
nsClientPlugin.nsClientService?.dbRemove("food", tt.first.interfaceIDs.nightscoutId, DataSyncSelector.PairFood(tt.first, tt.second)) food.first.interfaceIDs.nightscoutId != null ->
// existing without nsId = create new nsClientPlugin.nsClientService?.dbUpdate("food", food.first.interfaceIDs.nightscoutId, food.first.toJson(false), DataSyncSelector.PairFood(food.first, food.second), "$startId/$lastDbId")
tt.first.isValid && tt.first.interfaceIDs.nightscoutId == null ->
nsClientPlugin.nsClientService?.dbAdd("food", tt.first.toJson(), DataSyncSelector.PairFood(tt.first, tt.second))
// existing with nsId = update
tt.first.isValid && tt.first.interfaceIDs.nightscoutId != null ->
nsClientPlugin.nsClientService?.dbUpdate("food", tt.first.interfaceIDs.nightscoutId, tt.first.toJson(), DataSyncSelector.PairFood(tt.first, tt.second))
} }
return true return true
} }
@ -241,25 +293,35 @@ class DataSyncSelectorImplementation @Inject constructor(
} }
} }
private var lastGvId = -1L
private var lastGvTime = -1L
override fun processChangedGlucoseValuesCompat(): Boolean { override fun processChangedGlucoseValuesCompat(): Boolean {
val startId = sp.getLong(R.string.key_ns_glucose_value_last_synced_id, 0) val lastDbIdWrapped = appRepository.getLastGlucoseValueIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
var startId = sp.getLong(R.string.key_ns_glucose_value_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_ns_glucose_value_last_synced_id, 0)
startId = 0
}
if (startId == lastGvId && dateUtil.now() - lastGvTime < 5000) return false
lastGvId = startId
lastGvTime = dateUtil.now()
appRepository.getNextSyncElementGlucoseValue(startId).blockingGet()?.let { gv -> appRepository.getNextSyncElementGlucoseValue(startId).blockingGet()?.let { gv ->
aapsLogger.info(LTag.DATABASE, "Loading GlucoseValue data Start: $startId ID: ${gv.first.id} HistoryID: ${gv.second} ") aapsLogger.info(LTag.DATABASE, "Loading GlucoseValue data Start: $startId ID: ${gv.first.id} HistoryID: ${gv.second} ")
if (activePlugin.activeBgSource.shouldUploadToNs(gv.first)) { if (activePlugin.activeBgSource.shouldUploadToNs(gv.first)) {
when { when {
// removed and not uploaded yet = ignore // without nsId = create new
!gv.first.isValid && gv.first.interfaceIDs.nightscoutId == null -> Any() gv.first.interfaceIDs.nightscoutId == null ->
// removed and already uploaded = send for removal nsClientPlugin.nsClientService?.dbAdd("entries", gv.first.toJson(true, dateUtil), DataSyncSelector.PairGlucoseValue(gv.first, gv.second), "$startId/$lastDbId")
!gv.first.isValid && gv.first.interfaceIDs.nightscoutId != null -> // with nsId = update
nsClientPlugin.nsClientService?.dbRemove("entries", gv.first.interfaceIDs.nightscoutId, DataSyncSelector.PairGlucoseValue(gv.first, gv.second)) gv.first.interfaceIDs.nightscoutId != null ->
// existing without nsId = create new nsClientPlugin.nsClientService?.dbUpdate("entries", gv.first.interfaceIDs.nightscoutId, gv.first.toJson(false, dateUtil), DataSyncSelector.PairGlucoseValue(gv.first, gv.second), "$startId/$lastDbId")
gv.first.isValid && gv.first.interfaceIDs.nightscoutId == null ->
nsClientPlugin.nsClientService?.dbAdd("entries", gv.first.toJson(dateUtil), DataSyncSelector.PairGlucoseValue(gv.first, gv.second))
// existing with nsId = update
gv.first.isValid && gv.first.interfaceIDs.nightscoutId != null ->
nsClientPlugin.nsClientService?.dbUpdate("entries", gv.first.interfaceIDs.nightscoutId, gv.first.toJson(dateUtil), DataSyncSelector.PairGlucoseValue(gv.first, gv.second))
} }
return true return true
} else {
confirmLastGlucoseValueIdIfGreater(gv.second)
lastGvId = -1
processChangedGlucoseValuesCompat()
} }
} }
return false return false
@ -280,22 +342,28 @@ class DataSyncSelectorImplementation @Inject constructor(
} }
} }
private var lastTeId = -1L
private var lastTeTime = -1L
override fun processChangedTherapyEventsCompat(): Boolean { override fun processChangedTherapyEventsCompat(): Boolean {
val startId = sp.getLong(R.string.key_ns_therapy_event_last_synced_id, 0) val lastDbIdWrapped = appRepository.getLastTherapyEventIdWrapped().blockingGet()
appRepository.getNextSyncElementTherapyEvent(startId).blockingGet()?.let { tt -> val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
aapsLogger.info(LTag.DATABASE, "Loading TherapyEvents data Start: $startId ID: ${tt.first.id} HistoryID: ${tt.second} ") var startId = sp.getLong(R.string.key_ns_therapy_event_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_ns_therapy_event_last_synced_id, 0)
startId = 0
}
if (startId == lastTeId && dateUtil.now() - lastTeTime < 5000) return false
lastTeId = startId
lastTeTime = dateUtil.now()
appRepository.getNextSyncElementTherapyEvent(startId).blockingGet()?.let { te ->
aapsLogger.info(LTag.DATABASE, "Loading TherapyEvents data Start: $startId ID: ${te.first.id} HistoryID: ${te.second} ")
when { when {
// removed and not uploaded yet = ignore // without nsId = create new
!tt.first.isValid && tt.first.interfaceIDs.nightscoutId == null -> Any() te.first.interfaceIDs.nightscoutId == null ->
// removed and already uploaded = send for removal nsClientPlugin.nsClientService?.dbAdd("treatments", te.first.toJson(true), DataSyncSelector.PairTherapyEvent(te.first, te.second), "$startId/$lastDbId")
!tt.first.isValid && tt.first.interfaceIDs.nightscoutId != null -> // nsId = update
nsClientPlugin.nsClientService?.dbRemove("treatments", tt.first.interfaceIDs.nightscoutId, DataSyncSelector.PairTherapyEvent(tt.first, tt.second)) te.first.interfaceIDs.nightscoutId != null ->
// existing without nsId = create new nsClientPlugin.nsClientService?.dbUpdate("treatments", te.first.interfaceIDs.nightscoutId, te.first.toJson(false), DataSyncSelector.PairTherapyEvent(te.first, te.second), "$startId/$lastDbId")
tt.first.isValid && tt.first.interfaceIDs.nightscoutId == null ->
nsClientPlugin.nsClientService?.dbAdd("treatments", tt.first.toJson(), DataSyncSelector.PairTherapyEvent(tt.first, tt.second))
// existing with nsId = update
tt.first.isValid && tt.first.interfaceIDs.nightscoutId != null ->
nsClientPlugin.nsClientService?.dbUpdate("treatments", tt.first.interfaceIDs.nightscoutId, tt.first.toJson(), DataSyncSelector.PairTherapyEvent(tt.first, tt.second))
} }
return true return true
} }
@ -316,14 +384,25 @@ class DataSyncSelectorImplementation @Inject constructor(
} }
} }
private var lastDsId = -1L
private var lastDsTime = -1L
override fun processChangedDeviceStatusesCompat(): Boolean { override fun processChangedDeviceStatusesCompat(): Boolean {
val startId = sp.getLong(R.string.key_ns_device_status_last_synced_id, 0) val lastDbIdWrapped = appRepository.getLastDeviceStatusIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
var startId = sp.getLong(R.string.key_ns_device_status_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_ns_device_status_last_synced_id, 0)
startId = 0
}
if (startId == lastDsId && dateUtil.now() - lastDsTime < 5000) return false
lastDsId = startId
lastDsTime = dateUtil.now()
appRepository.getNextSyncElementDeviceStatus(startId).blockingGet()?.let { deviceStatus -> appRepository.getNextSyncElementDeviceStatus(startId).blockingGet()?.let { deviceStatus ->
aapsLogger.info(LTag.DATABASE, "Loading DeviceStatus data Start: $startId ID: ${deviceStatus.id}") aapsLogger.info(LTag.DATABASE, "Loading DeviceStatus data Start: $startId ID: ${deviceStatus.id}")
when { when {
// without nsId = create new // without nsId = create new
deviceStatus.interfaceIDs.nightscoutId == null -> deviceStatus.interfaceIDs.nightscoutId == null ->
nsClientPlugin.nsClientService?.dbAdd("devicestatus", deviceStatus.toJson(dateUtil), deviceStatus) nsClientPlugin.nsClientService?.dbAdd("devicestatus", deviceStatus.toJson(dateUtil), deviceStatus, "$startId/$lastDbId")
// with nsId = ignore // with nsId = ignore
deviceStatus.interfaceIDs.nightscoutId != null -> Any() deviceStatus.interfaceIDs.nightscoutId != null -> Any()
} }
@ -347,25 +426,37 @@ class DataSyncSelectorImplementation @Inject constructor(
} }
} }
private var lastTbrId = -1L
private var lastTbrTime = -1L
override fun processChangedTemporaryBasalsCompat(): Boolean { override fun processChangedTemporaryBasalsCompat(): Boolean {
val startId = sp.getLong(R.string.key_ns_temporary_basal_last_synced_id, 0) val useAbsolute = sp.getBoolean(R.string.key_ns_sync_use_absolute, false)
val lastDbIdWrapped = appRepository.getLastTemporaryBasalIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
var startId = sp.getLong(R.string.key_ns_temporary_basal_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_ns_temporary_basal_last_synced_id, 0)
startId = 0
}
if (startId == lastTbrId && dateUtil.now() - lastTbrTime < 5000) return false
lastTbrId = startId
lastTbrTime = dateUtil.now()
appRepository.getNextSyncElementTemporaryBasal(startId).blockingGet()?.let { tb -> appRepository.getNextSyncElementTemporaryBasal(startId).blockingGet()?.let { tb ->
aapsLogger.info(LTag.DATABASE, "Loading TemporaryBasal data Start: $startId ID: ${tb.first.id} HistoryID: ${tb.second} ") aapsLogger.info(LTag.DATABASE, "Loading TemporaryBasal data Start: $startId ID: ${tb.first.id} HistoryID: ${tb.second} ")
profileFunction.getProfile(tb.first.timestamp)?.let { profile -> val profile = profileFunction.getProfile(tb.first.timestamp)
if (profile != null) {
when { when {
// removed and not uploaded yet = ignore // without nsId = create new
!tb.first.isValid && tb.first.interfaceIDs.nightscoutId == null -> Any() tb.first.interfaceIDs.nightscoutId == null ->
// removed and already uploaded = send for removal nsClientPlugin.nsClientService?.dbAdd("treatments", tb.first.toJson(true, profile, dateUtil, useAbsolute), DataSyncSelector.PairTemporaryBasal(tb.first, tb.second), "$startId/$lastDbId")
!tb.first.isValid && tb.first.interfaceIDs.nightscoutId != null -> // with nsId = update
nsClientPlugin.nsClientService?.dbRemove("treatments", tb.first.interfaceIDs.nightscoutId, DataSyncSelector.PairTemporaryBasal(tb.first, tb.second)) tb.first.interfaceIDs.nightscoutId != null ->
// existing without nsId = create new nsClientPlugin.nsClientService?.dbUpdate("treatments", tb.first.interfaceIDs.nightscoutId, tb.first.toJson(false, profile, dateUtil, useAbsolute), DataSyncSelector.PairTemporaryBasal(tb.first, tb.second), "$startId/$lastDbId")
tb.first.isValid && tb.first.interfaceIDs.nightscoutId == null ->
nsClientPlugin.nsClientService?.dbAdd("treatments", tb.first.toJson(profile, dateUtil), DataSyncSelector.PairTemporaryBasal(tb.first, tb.second))
// existing with nsId = update
tb.first.isValid && tb.first.interfaceIDs.nightscoutId != null ->
nsClientPlugin.nsClientService?.dbUpdate("treatments", tb.first.interfaceIDs.nightscoutId, tb.first.toJson(profile, dateUtil), DataSyncSelector.PairTemporaryBasal(tb.first, tb.second))
} }
return true return true
} else {
confirmLastTemporaryBasalIdIfGreater(tb.second)
lastTbrId = -1
processChangedTemporaryBasalsCompat()
} }
} }
return false return false
@ -386,25 +477,37 @@ class DataSyncSelectorImplementation @Inject constructor(
} }
} }
private var lastEbId = -1L
private var lastEbTime = -1L
override fun processChangedExtendedBolusesCompat(): Boolean { override fun processChangedExtendedBolusesCompat(): Boolean {
val startId = sp.getLong(R.string.key_ns_extended_bolus_last_synced_id, 0) val useAbsolute = sp.getBoolean(R.string.key_ns_sync_use_absolute, false)
val lastDbIdWrapped = appRepository.getLastExtendedBolusIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
var startId = sp.getLong(R.string.key_ns_extended_bolus_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_ns_extended_bolus_last_synced_id, 0)
startId = 0
}
if (startId == lastEbId && dateUtil.now() - lastEbTime < 5000) return false
lastEbId = startId
lastEbTime = dateUtil.now()
appRepository.getNextSyncElementExtendedBolus(startId).blockingGet()?.let { eb -> appRepository.getNextSyncElementExtendedBolus(startId).blockingGet()?.let { eb ->
aapsLogger.info(LTag.DATABASE, "Loading ExtendedBolus data Start: $startId ID: ${eb.first.id} HistoryID: ${eb.second} ") aapsLogger.info(LTag.DATABASE, "Loading ExtendedBolus data Start: $startId ID: ${eb.first.id} HistoryID: ${eb.second} ")
profileFunction.getProfile(eb.first.timestamp)?.let { profile -> val profile = profileFunction.getProfile(eb.first.timestamp)
if (profile != null) {
when { when {
// removed and not uploaded yet = ignore // without nsId = create new
!eb.first.isValid && eb.first.interfaceIDs.nightscoutId == null -> Any() eb.first.interfaceIDs.nightscoutId == null ->
// removed and already uploaded = send for removal nsClientPlugin.nsClientService?.dbAdd("treatments", eb.first.toJson(true, profile, dateUtil, useAbsolute), DataSyncSelector.PairExtendedBolus(eb.first, eb.second), "$startId/$lastDbId")
!eb.first.isValid && eb.first.interfaceIDs.nightscoutId != null -> // with nsId = update
nsClientPlugin.nsClientService?.dbRemove("treatments", eb.first.interfaceIDs.nightscoutId, DataSyncSelector.PairExtendedBolus(eb.first, eb.second)) eb.first.interfaceIDs.nightscoutId != null ->
// existing without nsId = create new nsClientPlugin.nsClientService?.dbUpdate("treatments", eb.first.interfaceIDs.nightscoutId, eb.first.toJson(false, profile, dateUtil, useAbsolute), DataSyncSelector.PairExtendedBolus(eb.first, eb.second), "$startId/$lastDbId")
eb.first.isValid && eb.first.interfaceIDs.nightscoutId == null ->
nsClientPlugin.nsClientService?.dbAdd("treatments", eb.first.toJson(profile, dateUtil), DataSyncSelector.PairExtendedBolus(eb.first, eb.second))
// existing with nsId = update
eb.first.isValid && eb.first.interfaceIDs.nightscoutId != null ->
nsClientPlugin.nsClientService?.dbUpdate("treatments", eb.first.interfaceIDs.nightscoutId, eb.first.toJson(profile, dateUtil), DataSyncSelector.PairExtendedBolus(eb.first, eb.second))
} }
return true return true
} else {
confirmLastExtendedBolusIdIfGreater(eb.second)
lastEbId = -1
processChangedExtendedBolusesCompat()
} }
} }
return false return false
@ -424,25 +527,88 @@ class DataSyncSelectorImplementation @Inject constructor(
} }
} }
private var lastPsId = -1L
private var lastPsTime = -1L
override fun processChangedProfileSwitchesCompat(): Boolean { override fun processChangedProfileSwitchesCompat(): Boolean {
val startId = sp.getLong(R.string.key_ns_profile_switch_last_synced_id, 0) val lastDbIdWrapped = appRepository.getLastProfileSwitchIdWrapped().blockingGet()
appRepository.getNextSyncElementProfileSwitch(startId).blockingGet()?.let { eb -> val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
aapsLogger.info(LTag.DATABASE, "Loading ProfileSwitch data Start: $startId ID: ${eb.first.id} HistoryID: ${eb.second} ") var startId = sp.getLong(R.string.key_ns_profile_switch_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_ns_profile_switch_last_synced_id, 0)
startId = 0
}
if (startId == lastPsId && dateUtil.now() - lastPsTime < 5000) return false
lastPsId = startId
lastPsTime = dateUtil.now()
appRepository.getNextSyncElementProfileSwitch(startId).blockingGet()?.let { ps ->
aapsLogger.info(LTag.DATABASE, "Loading ProfileSwitch data Start: $startId ID: ${ps.first.id} HistoryID: ${ps.second} ")
when { when {
// removed and not uploaded yet = ignore // without nsId = create new
!eb.first.isValid && eb.first.interfaceIDs.nightscoutId == null -> Any() ps.first.interfaceIDs.nightscoutId == null ->
// removed and already uploaded = send for removal nsClientPlugin.nsClientService?.dbAdd("treatments", ps.first.toJson(true, dateUtil), DataSyncSelector.PairProfileSwitch(ps.first, ps.second), "$startId/$lastDbId")
!eb.first.isValid && eb.first.interfaceIDs.nightscoutId != null -> // with nsId = update
nsClientPlugin.nsClientService?.dbRemove("treatments", eb.first.interfaceIDs.nightscoutId, DataSyncSelector.PairProfileSwitch(eb.first, eb.second)) ps.first.interfaceIDs.nightscoutId != null ->
// existing without nsId = create new nsClientPlugin.nsClientService?.dbUpdate("treatments", ps.first.interfaceIDs.nightscoutId, ps.first.toJson(false, dateUtil), DataSyncSelector.PairProfileSwitch(ps.first, ps.second), "$startId/$lastDbId")
eb.first.isValid && eb.first.interfaceIDs.nightscoutId == null ->
nsClientPlugin.nsClientService?.dbAdd("treatments", eb.first.toJson(dateUtil), DataSyncSelector.PairProfileSwitch(eb.first, eb.second))
// existing with nsId = update
eb.first.isValid && eb.first.interfaceIDs.nightscoutId != null ->
nsClientPlugin.nsClientService?.dbUpdate("treatments", eb.first.interfaceIDs.nightscoutId, eb.first.toJson(dateUtil), DataSyncSelector.PairProfileSwitch(eb.first, eb.second))
} }
return true return true
} }
return false return false
} }
override fun confirmLastOfflineEventIdIfGreater(lastSynced: Long) {
if (lastSynced > sp.getLong(R.string.key_ns_offline_event_last_synced_id, 0)) {
aapsLogger.debug(LTag.NSCLIENT, "Setting OfflineEvent data sync from $lastSynced")
sp.putLong(R.string.key_ns_offline_event_last_synced_id, lastSynced)
}
}
// Prepared for v3 (returns all modified after)
override fun changedOfflineEvents(): List<OfflineEvent> {
val startId = sp.getLong(R.string.key_ns_offline_event_last_synced_id, 0)
return appRepository.getModifiedOfflineEventsDataFromId(startId).blockingGet().also {
aapsLogger.debug(LTag.NSCLIENT, "Loading OfflineEvent data for sync from $startId. Records ${it.size}")
}
}
private var lastOeId = -1L
private var lastOeTime = -1L
override fun processChangedOfflineEventsCompat(): Boolean {
val lastDbIdWrapped = appRepository.getLastOfflineEventIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
var startId = sp.getLong(R.string.key_ns_offline_event_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_ns_offline_event_last_synced_id, 0)
startId = 0
}
if (startId == lastOeId && dateUtil.now() - lastOeTime < 5000) return false
lastOeId = startId
lastOeTime = dateUtil.now()
appRepository.getNextSyncElementOfflineEvent(startId).blockingGet()?.let { oe ->
aapsLogger.info(LTag.DATABASE, "Loading OfflineEvent data Start: $startId ID: ${oe.first.id} HistoryID: ${oe.second} ")
when {
// without nsId = create new
oe.first.interfaceIDs.nightscoutId == null ->
nsClientPlugin.nsClientService?.dbAdd("treatments", oe.first.toJson(true, dateUtil), DataSyncSelector.PairOfflineEvent(oe.first, oe.second), "$startId/$lastDbId")
// existing with nsId = update
oe.first.interfaceIDs.nightscoutId != null ->
nsClientPlugin.nsClientService?.dbUpdate("treatments", oe.first.interfaceIDs.nightscoutId, oe.first.toJson(false, dateUtil), DataSyncSelector.PairOfflineEvent(oe.first, oe.second), "$startId/$lastDbId")
}
return true
}
return false
}
override fun confirmLastProfileStore(lastSynced: Long) {
sp.putLong(R.string.key_ns_profile_store_last_synced_timestamp, lastSynced)
}
override fun processChangedProfileStore() {
val lastSync = sp.getLong(R.string.key_ns_profile_store_last_synced_timestamp, 0)
val lastChange = sp.getLong(R.string.key_local_profile_last_change, 0)
if (lastChange == 0L) return
if (lastChange > lastSync) {
val profileJson = localProfilePlugin.profile?.data ?: return
nsClientPlugin.nsClientService?.dbAdd("profile", profileJson, DataSyncSelector.PairProfileStore(profileJson, dateUtil.now()), "")
}
}
} }

View file

@ -147,7 +147,7 @@ class NSClientAddAckWorker(
dataSyncSelector.confirmLastCarbsIdIfGreater(pair.updateRecordId) dataSyncSelector.confirmLastCarbsIdIfGreater(pair.updateRecordId)
} }
.blockingGet() .blockingGet()
rxBus.send(EventNSClientNewLog("DBADD", "Acked Carbs" + pair.value.interfaceIDs.nightscoutId)) rxBus.send(EventNSClientNewLog("DBADD", "Acked Carbs " + pair.value.interfaceIDs.nightscoutId))
// Send new if waiting // Send new if waiting
dataSyncSelector.processChangedCarbsCompat() dataSyncSelector.processChangedCarbsCompat()
} }
@ -166,7 +166,7 @@ class NSClientAddAckWorker(
dataSyncSelector.confirmLastBolusCalculatorResultsIdIfGreater(pair.updateRecordId) dataSyncSelector.confirmLastBolusCalculatorResultsIdIfGreater(pair.updateRecordId)
} }
.blockingGet() .blockingGet()
rxBus.send(EventNSClientNewLog("DBADD", "Acked BolusCalculatorResult" + pair.value.interfaceIDs.nightscoutId)) rxBus.send(EventNSClientNewLog("DBADD", "Acked BolusCalculatorResult " + pair.value.interfaceIDs.nightscoutId))
// Send new if waiting // Send new if waiting
dataSyncSelector.processChangedBolusCalculatorResultsCompat() dataSyncSelector.processChangedBolusCalculatorResultsCompat()
} }
@ -185,7 +185,7 @@ class NSClientAddAckWorker(
dataSyncSelector.confirmLastTemporaryBasalIdIfGreater(pair.updateRecordId) dataSyncSelector.confirmLastTemporaryBasalIdIfGreater(pair.updateRecordId)
} }
.blockingGet() .blockingGet()
rxBus.send(EventNSClientNewLog("DBADD", "Acked TemporaryBasal" + pair.value.interfaceIDs.nightscoutId)) rxBus.send(EventNSClientNewLog("DBADD", "Acked TemporaryBasal " + pair.value.interfaceIDs.nightscoutId))
// Send new if waiting // Send new if waiting
dataSyncSelector.processChangedTemporaryBasalsCompat() dataSyncSelector.processChangedTemporaryBasalsCompat()
} }
@ -204,9 +204,9 @@ class NSClientAddAckWorker(
dataSyncSelector.confirmLastExtendedBolusIdIfGreater(pair.updateRecordId) dataSyncSelector.confirmLastExtendedBolusIdIfGreater(pair.updateRecordId)
} }
.blockingGet() .blockingGet()
rxBus.send(EventNSClientNewLog("DBADD", "Acked ExtendedBolus" + pair.value.interfaceIDs.nightscoutId)) rxBus.send(EventNSClientNewLog("DBADD", "Acked ExtendedBolus " + pair.value.interfaceIDs.nightscoutId))
// Send new if waiting // Send new if waiting
dataSyncSelector.processChangedTemporaryBasalsCompat() dataSyncSelector.processChangedExtendedBolusesCompat()
} }
is PairProfileSwitch -> { is PairProfileSwitch -> {
@ -223,9 +223,9 @@ class NSClientAddAckWorker(
dataSyncSelector.confirmLastProfileSwitchIdIfGreater(pair.updateRecordId) dataSyncSelector.confirmLastProfileSwitchIdIfGreater(pair.updateRecordId)
} }
.blockingGet() .blockingGet()
rxBus.send(EventNSClientNewLog("DBADD", "Acked ProfileSwitch" + pair.value.interfaceIDs.nightscoutId)) rxBus.send(EventNSClientNewLog("DBADD", "Acked ProfileSwitch " + pair.value.interfaceIDs.nightscoutId))
// Send new if waiting // Send new if waiting
dataSyncSelector.processChangedTemporaryBasalsCompat() dataSyncSelector.processChangedProfileSwitchesCompat()
} }
is DeviceStatus -> { is DeviceStatus -> {
@ -242,10 +242,35 @@ class NSClientAddAckWorker(
dataSyncSelector.confirmLastDeviceStatusIdIfGreater(deviceStatus.id) dataSyncSelector.confirmLastDeviceStatusIdIfGreater(deviceStatus.id)
} }
.blockingGet() .blockingGet()
rxBus.send(EventNSClientNewLog("DBADD", "Acked DeviceStatus" + deviceStatus.interfaceIDs.nightscoutId)) rxBus.send(EventNSClientNewLog("DBADD", "Acked DeviceStatus " + deviceStatus.interfaceIDs.nightscoutId))
// Send new if waiting // Send new if waiting
dataSyncSelector.processChangedDeviceStatusesCompat() dataSyncSelector.processChangedDeviceStatusesCompat()
} }
is PairProfileStore -> {
dataSyncSelector.confirmLastProfileStore(ack.originalObject.timestampSync)
rxBus.send(EventNSClientNewLog("DBADD", "Acked ProfileStore " + ack.id))
}
is PairOfflineEvent -> {
val pair = ack.originalObject
pair.value.interfaceIDs.nightscoutId = ack.id
repository.runTransactionForResult(UpdateNsIdOfflineEventTransaction(pair.value))
.doOnError { error ->
aapsLogger.error(LTag.DATABASE, "Updated ns id of OfflineEvent failed", error)
ret = Result.failure((workDataOf("Error" to error.toString())))
}
.doOnSuccess {
ret = Result.success(workDataOf("ProcessedData" to pair.toString()))
aapsLogger.debug(LTag.DATABASE, "Updated ns id of OfflineEvent " + pair.value)
dataSyncSelector.confirmLastOfflineEventIdIfGreater(pair.updateRecordId)
}
.blockingGet()
rxBus.send(EventNSClientNewLog("DBADD", "Acked OfflineEvent " + pair.value.interfaceIDs.nightscoutId))
// Send new if waiting
dataSyncSelector.processChangedOfflineEventsCompat()
}
} }
return ret return ret
} }

View file

@ -14,6 +14,7 @@ import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.transactions.* import info.nightscout.androidaps.database.transactions.*
import info.nightscout.androidaps.extensions.* import info.nightscout.androidaps.extensions.*
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
@ -43,13 +44,11 @@ class NSClientAddUpdateWorker(
@Inject lateinit var dateUtil: DateUtil @Inject lateinit var dateUtil: DateUtil
@Inject lateinit var config: Config @Inject lateinit var config: Config
@Inject lateinit var repository: AppRepository @Inject lateinit var repository: AppRepository
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var rxBus: RxBusWrapper @Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var uel: UserEntryLogger @Inject lateinit var uel: UserEntryLogger
override fun doWork(): Result { override fun doWork(): Result {
val acceptNSData = !sp.getBoolean(R.string.key_ns_upload_only, true) && buildHelper.isEngineeringMode() || config.NSCLIENT
if (!acceptNSData) return Result.success()
val treatments = dataWorker.pickupJSONArray(inputData.getLong(DataWorker.STORE_KEY, -1)) val treatments = dataWorker.pickupJSONArray(inputData.getLong(DataWorker.STORE_KEY, -1))
?: return Result.failure(workDataOf("Error" to "missing input data")) ?: return Result.failure(workDataOf("Error" to "missing input data"))
@ -73,6 +72,7 @@ class NSClientAddUpdateWorker(
if (mills > latestDateInReceivedData) latestDateInReceivedData = mills if (mills > latestDateInReceivedData) latestDateInReceivedData = mills
if (insulin > 0) { if (insulin > 0) {
if (sp.getBoolean(R.string.key_ns_receive_insulin, false) && buildHelper.isEngineeringMode() || config.NSCLIENT) {
bolusFromJson(json)?.let { bolus -> bolusFromJson(json)?.let { bolus ->
repository.runTransactionForResult(SyncNsBolusTransaction(bolus, invalidateByNsOnly = false)) repository.runTransactionForResult(SyncNsBolusTransaction(bolus, invalidateByNsOnly = false))
.doOnError { .doOnError {
@ -101,7 +101,9 @@ class NSClientAddUpdateWorker(
} }
} ?: aapsLogger.error("Error parsing bolus json $json") } ?: aapsLogger.error("Error parsing bolus json $json")
} }
}
if (carbs > 0) { if (carbs > 0) {
if (sp.getBoolean(R.string.key_ns_receive_carbs, false) && buildHelper.isEngineeringMode() || config.NSCLIENT) {
carbsFromJson(json)?.let { carb -> carbsFromJson(json)?.let { carb ->
repository.runTransactionForResult(SyncNsCarbsTransaction(carb, invalidateByNsOnly = false)) repository.runTransactionForResult(SyncNsCarbsTransaction(carb, invalidateByNsOnly = false))
.doOnError { .doOnError {
@ -130,6 +132,7 @@ class NSClientAddUpdateWorker(
} }
} ?: aapsLogger.error("Error parsing bolus json $json") } ?: aapsLogger.error("Error parsing bolus json $json")
} }
}
// Convert back emulated TBR -> EB // Convert back emulated TBR -> EB
if (eventType == TherapyEvent.Type.TEMPORARY_BASAL.text && json.has("extendedEmulated")) { if (eventType == TherapyEvent.Type.TEMPORARY_BASAL.text && json.has("extendedEmulated")) {
val ebJson = json.getJSONObject("extendedEmulated") val ebJson = json.getJSONObject("extendedEmulated")
@ -140,6 +143,7 @@ class NSClientAddUpdateWorker(
when { when {
insulin > 0 || carbs > 0 -> Any() insulin > 0 || carbs > 0 -> Any()
eventType == TherapyEvent.Type.TEMPORARY_TARGET.text -> eventType == TherapyEvent.Type.TEMPORARY_TARGET.text ->
if (sp.getBoolean(R.string.key_ns_receive_temp_target, false) && buildHelper.isEngineeringMode() || config.NSCLIENT) {
temporaryTargetFromJson(json)?.let { temporaryTarget -> temporaryTargetFromJson(json)?.let { temporaryTarget ->
repository.runTransactionForResult(SyncNsTemporaryTargetTransaction(temporaryTarget, invalidateByNsOnly = false)) repository.runTransactionForResult(SyncNsTemporaryTargetTransaction(temporaryTarget, invalidateByNsOnly = false))
.doOnError { .doOnError {
@ -180,6 +184,7 @@ class NSClientAddUpdateWorker(
} }
} }
} ?: aapsLogger.error("Error parsing TT json $json") } ?: aapsLogger.error("Error parsing TT json $json")
}
eventType == TherapyEvent.Type.CANNULA_CHANGE.text || eventType == TherapyEvent.Type.CANNULA_CHANGE.text ||
eventType == TherapyEvent.Type.INSULIN_CHANGE.text || eventType == TherapyEvent.Type.INSULIN_CHANGE.text ||
eventType == TherapyEvent.Type.SENSOR_CHANGE.text || eventType == TherapyEvent.Type.SENSOR_CHANGE.text ||
@ -188,8 +193,8 @@ class NSClientAddUpdateWorker(
eventType == TherapyEvent.Type.ANNOUNCEMENT.text || eventType == TherapyEvent.Type.ANNOUNCEMENT.text ||
eventType == TherapyEvent.Type.QUESTION.text || eventType == TherapyEvent.Type.QUESTION.text ||
eventType == TherapyEvent.Type.EXERCISE.text || eventType == TherapyEvent.Type.EXERCISE.text ||
eventType == TherapyEvent.Type.APS_OFFLINE.text ||
eventType == TherapyEvent.Type.PUMP_BATTERY_CHANGE.text -> eventType == TherapyEvent.Type.PUMP_BATTERY_CHANGE.text ->
if (sp.getBoolean(R.string.key_ns_receive_therapy_events, false) || config.NSCLIENT) {
therapyEventFromJson(json)?.let { therapyEvent -> therapyEventFromJson(json)?.let { therapyEvent ->
repository.runTransactionForResult(SyncNsTherapyEventTransaction(therapyEvent, invalidateByNsOnly = false)) repository.runTransactionForResult(SyncNsTherapyEventTransaction(therapyEvent, invalidateByNsOnly = false))
.doOnError { .doOnError {
@ -224,7 +229,9 @@ class NSClientAddUpdateWorker(
} }
} }
} ?: aapsLogger.error("Error parsing TherapyEvent json $json") } ?: aapsLogger.error("Error parsing TherapyEvent json $json")
}
eventType == TherapyEvent.Type.COMBO_BOLUS.text -> eventType == TherapyEvent.Type.COMBO_BOLUS.text ->
if (config.NSCLIENT) {
extendedBolusFromJson(json)?.let { extendedBolus -> extendedBolusFromJson(json)?.let { extendedBolus ->
repository.runTransactionForResult(SyncNsExtendedBolusTransaction(extendedBolus, invalidateByNsOnly = false)) repository.runTransactionForResult(SyncNsExtendedBolusTransaction(extendedBolus, invalidateByNsOnly = false))
.doOnError { .doOnError {
@ -265,7 +272,9 @@ class NSClientAddUpdateWorker(
} }
} }
} ?: aapsLogger.error("Error parsing ExtendedBolus json $json") } ?: aapsLogger.error("Error parsing ExtendedBolus json $json")
}
eventType == TherapyEvent.Type.TEMPORARY_BASAL.text -> eventType == TherapyEvent.Type.TEMPORARY_BASAL.text ->
if (config.NSCLIENT) {
temporaryBasalFromJson(json)?.let { temporaryBasal -> temporaryBasalFromJson(json)?.let { temporaryBasal ->
repository.runTransactionForResult(SyncNsTemporaryBasalTransaction(temporaryBasal, invalidateByNsOnly = false)) repository.runTransactionForResult(SyncNsTemporaryBasalTransaction(temporaryBasal, invalidateByNsOnly = false))
.doOnError { .doOnError {
@ -277,7 +286,7 @@ class NSClientAddUpdateWorker(
result.inserted.forEach { result.inserted.forEach {
uel.log(Action.TEMP_BASAL, Sources.NSClient, uel.log(Action.TEMP_BASAL, Sources.NSClient,
ValueWithUnit.Timestamp(it.timestamp), ValueWithUnit.Timestamp(it.timestamp),
ValueWithUnit.UnitPerHour(it.rate), if (it.isAbsolute) ValueWithUnit.UnitPerHour(it.rate) else ValueWithUnit.Percent(it.rate.toInt()),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt()) ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt())
) )
aapsLogger.debug(LTag.DATABASE, "Inserted TemporaryBasal $it") aapsLogger.debug(LTag.DATABASE, "Inserted TemporaryBasal $it")
@ -285,7 +294,7 @@ class NSClientAddUpdateWorker(
result.invalidated.forEach { result.invalidated.forEach {
uel.log(Action.TEMP_BASAL_REMOVED, Sources.NSClient, uel.log(Action.TEMP_BASAL_REMOVED, Sources.NSClient,
ValueWithUnit.Timestamp(it.timestamp), ValueWithUnit.Timestamp(it.timestamp),
ValueWithUnit.UnitPerHour(it.rate), if (it.isAbsolute) ValueWithUnit.UnitPerHour(it.rate) else ValueWithUnit.Percent(it.rate.toInt()),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt()) ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt())
) )
aapsLogger.debug(LTag.DATABASE, "Invalidated TemporaryBasal $it") aapsLogger.debug(LTag.DATABASE, "Invalidated TemporaryBasal $it")
@ -293,7 +302,7 @@ class NSClientAddUpdateWorker(
result.ended.forEach { result.ended.forEach {
uel.log(Action.CANCEL_TEMP_BASAL, Sources.NSClient, uel.log(Action.CANCEL_TEMP_BASAL, Sources.NSClient,
ValueWithUnit.Timestamp(it.timestamp), ValueWithUnit.Timestamp(it.timestamp),
ValueWithUnit.UnitPerHour(it.rate), if (it.isAbsolute) ValueWithUnit.UnitPerHour(it.rate) else ValueWithUnit.Percent(it.rate.toInt()),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt()) ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt())
) )
aapsLogger.debug(LTag.DATABASE, "Ended TemporaryBasal $it") aapsLogger.debug(LTag.DATABASE, "Ended TemporaryBasal $it")
@ -303,8 +312,10 @@ class NSClientAddUpdateWorker(
} }
} }
} ?: aapsLogger.error("Error parsing TemporaryBasal json $json") } ?: aapsLogger.error("Error parsing TemporaryBasal json $json")
}
eventType == TherapyEvent.Type.PROFILE_SWITCH.text -> eventType == TherapyEvent.Type.PROFILE_SWITCH.text ->
profileSwitchFromJson(json, dateUtil)?.let { profileSwitch -> if (sp.getBoolean(R.string.key_ns_receive_profile_switch, false) && buildHelper.isEngineeringMode() || config.NSCLIENT) {
profileSwitchFromJson(json, dateUtil, activePlugin)?.let { profileSwitch ->
repository.runTransactionForResult(SyncNsProfileSwitchTransaction(profileSwitch, invalidateByNsOnly = false)) repository.runTransactionForResult(SyncNsProfileSwitchTransaction(profileSwitch, invalidateByNsOnly = false))
.doOnError { .doOnError {
aapsLogger.error(LTag.DATABASE, "Error while saving ProfileSwitch", it) aapsLogger.error(LTag.DATABASE, "Error while saving ProfileSwitch", it)
@ -326,8 +337,47 @@ class NSClientAddUpdateWorker(
aapsLogger.debug(LTag.DATABASE, "Updated nsId ProfileSwitch $it") aapsLogger.debug(LTag.DATABASE, "Updated nsId ProfileSwitch $it")
} }
} }
} ?: aapsLogger.error("Error parsing TemporaryBasal json $json") } ?: aapsLogger.error("Error parsing ProfileSwitch json $json")
} }
eventType == TherapyEvent.Type.APS_OFFLINE.text ->
if (sp.getBoolean(R.string.key_ns_receive_offline_event, false) && buildHelper.isEngineeringMode() || config.NSCLIENT) {
offlineEventFromJson(json)?.let { offlineEvent ->
repository.runTransactionForResult(SyncNsOfflineEventTransaction(offlineEvent, invalidateByNsOnly = false))
.doOnError {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
ret = Result.failure(workDataOf("Error" to it.toString()))
}
.blockingGet()
.also { result ->
result.inserted.forEach { oe ->
uel.log(Action.LOOP_CHANGE, Sources.NSClient,
ValueWithUnit.OfflineEventReason(oe.reason),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(oe.duration).toInt())
)
aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $oe")
}
result.invalidated.forEach { oe ->
uel.log(Action.LOOP_REMOVED, Sources.NSClient,
ValueWithUnit.OfflineEventReason(oe.reason),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(oe.duration).toInt())
)
aapsLogger.debug(LTag.DATABASE, "Invalidated OfflineEvent $oe")
}
result.ended.forEach { oe ->
uel.log(Action.LOOP_CHANGE, Sources.NSClient,
ValueWithUnit.OfflineEventReason(oe.reason),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(oe.duration).toInt())
)
aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $oe")
}
result.updatedNsId.forEach {
aapsLogger.debug(LTag.DATABASE, "Updated nsId OfflineEvent $it")
}
}
} ?: aapsLogger.error("Error parsing OfflineEvent json $json")
}
}
if (sp.getBoolean(R.string.key_ns_receive_therapy_events, false) || config.NSCLIENT)
if (eventType == TherapyEvent.Type.ANNOUNCEMENT.text) { if (eventType == TherapyEvent.Type.ANNOUNCEMENT.text) {
val date = safeGetLong(json, "mills") val date = safeGetLong(json, "mills")
val now = System.currentTimeMillis() val now = System.currentTimeMillis()

View file

@ -12,14 +12,11 @@ import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.databinding.NsClientFragmentBinding import info.nightscout.androidaps.databinding.NsClientFragmentBinding
import info.nightscout.androidaps.interfaces.DataSyncSelector import info.nightscout.androidaps.interfaces.DataSyncSelector
import info.nightscout.androidaps.interfaces.UploadQueueAdminInterface
import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientNewLog
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientUpdateGUI import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientUpdateGUI
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.HtmlHelper.fromHtml
import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.rx.AapsSchedulers
@ -33,7 +30,6 @@ class NSClientFragment : DaggerFragment() {
@Inject lateinit var sp: SP @Inject lateinit var sp: SP
@Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var rxBus: RxBusWrapper @Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var uploadQueue: UploadQueueAdminInterface
@Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var aapsSchedulers: AapsSchedulers @Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var dataSyncSelector: DataSyncSelector @Inject lateinit var dataSyncSelector: DataSyncSelector
@ -72,18 +68,6 @@ class NSClientFragment : DaggerFragment() {
binding.restart.paintFlags = binding.restart.paintFlags or Paint.UNDERLINE_TEXT_FLAG binding.restart.paintFlags = binding.restart.paintFlags or Paint.UNDERLINE_TEXT_FLAG
binding.deliverNow.setOnClickListener { nsClientPlugin.resend("GUI") } binding.deliverNow.setOnClickListener { nsClientPlugin.resend("GUI") }
binding.deliverNow.paintFlags = binding.deliverNow.paintFlags or Paint.UNDERLINE_TEXT_FLAG binding.deliverNow.paintFlags = binding.deliverNow.paintFlags or Paint.UNDERLINE_TEXT_FLAG
binding.clearQueue.setOnClickListener {
context?.let { context ->
OKDialog.showConfirmation(context, resourceHelper.gs(R.string.nsclientinternal), resourceHelper.gs(R.string.clearqueueconfirm), Runnable {
uel.log(Action.NS_QUEUE_CLEARED, Sources.NSClient)
uploadQueue.clearQueue()
updateGui()
})
}
}
binding.clearQueue.paintFlags = binding.clearQueue.paintFlags or Paint.UNDERLINE_TEXT_FLAG
binding.showQueue.setOnClickListener { rxBus.send(EventNSClientNewLog("QUEUE", uploadQueue.textList())) }
binding.showQueue.paintFlags = binding.showQueue.paintFlags or Paint.UNDERLINE_TEXT_FLAG
binding.fullSync.setOnClickListener { binding.fullSync.setOnClickListener {
context?.let { context -> context?.let { context ->
OKDialog.showConfirmation(context, resourceHelper.gs(R.string.nsclientinternal), resourceHelper.gs(R.string.full_sync), Runnable { OKDialog.showConfirmation(context, resourceHelper.gs(R.string.nsclientinternal), resourceHelper.gs(R.string.full_sync), Runnable {
@ -116,7 +100,6 @@ class NSClientFragment : DaggerFragment() {
binding.log.text = nsClientPlugin.textLog binding.log.text = nsClientPlugin.textLog
if (nsClientPlugin.autoscroll) binding.logScrollview.fullScroll(ScrollView.FOCUS_DOWN) if (nsClientPlugin.autoscroll) binding.logScrollview.fullScroll(ScrollView.FOCUS_DOWN)
binding.url.text = nsClientPlugin.url() binding.url.text = nsClientPlugin.url()
binding.queue.text = fromHtml(resourceHelper.gs(R.string.queue) + " <b>" + uploadQueue.size() + "</b>")
binding.status.text = nsClientPlugin.status binding.status.text = nsClientPlugin.status
} }
} }

View file

@ -8,13 +8,13 @@ import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.transactions.SyncNsTherapyEventTransaction import info.nightscout.androidaps.database.transactions.SyncNsTherapyEventTransaction
import info.nightscout.androidaps.extensions.therapyEventFromNsMbg
import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.general.nsclient.data.NSMbg import info.nightscout.androidaps.plugins.general.nsclient.data.NSMbg
import info.nightscout.androidaps.receivers.DataWorker import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.extensions.therapyEventFromNsMbg
import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.sharedPreferences.SP
import javax.inject.Inject import javax.inject.Inject
@ -33,8 +33,8 @@ class NSClientMbgWorker(
override fun doWork(): Result { override fun doWork(): Result {
var ret = Result.success() var ret = Result.success()
val acceptNSData = !sp.getBoolean(R.string.key_ns_upload_only, true) && buildHelper.isEngineeringMode() || config.NSCLIENT val acceptNSData = sp.getBoolean(R.string.key_ns_receive_therapy_events, false) || config.NSCLIENT
if (!acceptNSData) return ret if (!acceptNSData) return Result.success(workDataOf("Result" to "Sync not enabled"))
val mbgArray = dataWorker.pickupJSONArray(inputData.getLong(DataWorker.STORE_KEY, -1)) val mbgArray = dataWorker.pickupJSONArray(inputData.getLong(DataWorker.STORE_KEY, -1))
?: return Result.failure(workDataOf("Error" to "missing input data")) ?: return Result.failure(workDataOf("Error" to "missing input data"))

View file

@ -1,320 +0,0 @@
package info.nightscout.androidaps.plugins.general.nsclient;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.text.Spanned;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreference;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.android.HasAndroidInjector;
import info.nightscout.androidaps.interfaces.Config;
import info.nightscout.androidaps.Constants;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.events.EventAppExit;
import info.nightscout.androidaps.events.EventChargingState;
import info.nightscout.androidaps.events.EventNetworkChange;
import info.nightscout.androidaps.events.EventPreferenceChange;
import info.nightscout.androidaps.interfaces.PluginBase;
import info.nightscout.androidaps.interfaces.PluginDescription;
import info.nightscout.androidaps.interfaces.PluginType;
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.nsclient.data.AlarmAck;
import info.nightscout.androidaps.plugins.general.nsclient.data.NSAlarm;
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientNewLog;
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientResend;
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus;
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientUpdateGUI;
import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientService;
import info.nightscout.androidaps.utils.FabricPrivacy;
import info.nightscout.androidaps.utils.HtmlHelper;
import info.nightscout.androidaps.utils.ToastUtils;
import info.nightscout.androidaps.utils.buildHelper.BuildHelper;
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;
@Singleton
public class NSClientPlugin extends PluginBase {
private final CompositeDisposable disposable = new CompositeDisposable();
private final AAPSLogger aapsLogger;
private final RxBusWrapper rxBus;
private final ResourceHelper resourceHelper;
private final Context context;
private final AapsSchedulers aapsSchedulers;
private final FabricPrivacy fabricPrivacy;
private final SP sp;
private final NsClientReceiverDelegate nsClientReceiverDelegate;
private final Config config;
private final BuildHelper buildHelper;
public Handler handler;
private final List<EventNSClientNewLog> listLog = new ArrayList<>();
Spanned textLog = HtmlHelper.INSTANCE.fromHtml("");
public boolean paused;
boolean autoscroll;
public String status = "";
public @Nullable NSClientService nsClientService = null;
@Inject
public NSClientPlugin(
HasAndroidInjector injector,
AAPSLogger aapsLogger,
AapsSchedulers aapsSchedulers,
RxBusWrapper rxBus,
ResourceHelper resourceHelper,
Context context,
FabricPrivacy fabricPrivacy,
SP sp,
NsClientReceiverDelegate nsClientReceiverDelegate,
Config config,
BuildHelper buildHelper
) {
super(new PluginDescription()
.mainType(PluginType.GENERAL)
.fragmentClass(NSClientFragment.class.getName())
.pluginIcon(R.drawable.ic_nightscout_syncs)
.pluginName(R.string.nsclientinternal)
.shortName(R.string.nsclientinternal_shortname)
.preferencesId(R.xml.pref_nsclientinternal)
.description(R.string.description_ns_client),
aapsLogger, resourceHelper, injector
);
this.aapsLogger = aapsLogger;
this.aapsSchedulers = aapsSchedulers;
this.rxBus = rxBus;
this.resourceHelper = resourceHelper;
this.context = context;
this.fabricPrivacy = fabricPrivacy;
this.sp = sp;
this.nsClientReceiverDelegate = nsClientReceiverDelegate;
this.config = config;
this.buildHelper = buildHelper;
if (config.getNSCLIENT()) {
getPluginDescription().alwaysEnabled(true).visibleByDefault(true);
}
if (handler == null) {
HandlerThread handlerThread = new HandlerThread(NSClientPlugin.class.getSimpleName() + "Handler");
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
}
}
public boolean isAllowed() {
return nsClientReceiverDelegate.allowed;
}
@Override
protected void onStart() {
paused = sp.getBoolean(R.string.key_nsclientinternal_paused, false);
autoscroll = sp.getBoolean(R.string.key_nsclientinternal_autoscroll, true);
Intent intent = new Intent(context, NSClientService.class);
context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
super.onStart();
nsClientReceiverDelegate.grabReceiversState();
disposable.add(rxBus
.toObservable(EventNSClientStatus.class)
.observeOn(aapsSchedulers.getIo())
.subscribe(event -> {
status = event.getStatus(resourceHelper);
rxBus.send(new EventNSClientUpdateGUI());
}, fabricPrivacy::logException)
);
disposable.add(rxBus
.toObservable(EventNetworkChange.class)
.observeOn(aapsSchedulers.getIo())
.subscribe(nsClientReceiverDelegate::onStatusEvent, fabricPrivacy::logException)
);
disposable.add(rxBus
.toObservable(EventPreferenceChange.class)
.observeOn(aapsSchedulers.getIo())
.subscribe(nsClientReceiverDelegate::onStatusEvent, fabricPrivacy::logException)
);
disposable.add(rxBus
.toObservable(EventAppExit.class)
.observeOn(aapsSchedulers.getIo())
.subscribe(event -> {
if (nsClientService != null) {
context.unbindService(mConnection);
}
}, fabricPrivacy::logException)
);
disposable.add(rxBus
.toObservable(EventNSClientNewLog.class)
.observeOn(aapsSchedulers.getIo())
.subscribe(event -> {
addToLog(event);
aapsLogger.debug(LTag.NSCLIENT, event.getAction() + " " + event.getLogText());
}, fabricPrivacy::logException)
);
disposable.add(rxBus
.toObservable(EventChargingState.class)
.observeOn(aapsSchedulers.getIo())
.subscribe(nsClientReceiverDelegate::onStatusEvent, fabricPrivacy::logException)
);
disposable.add(rxBus
.toObservable(EventNSClientResend.class)
.observeOn(aapsSchedulers.getIo())
.subscribe(event -> resend(event.getReason()), fabricPrivacy::logException)
);
}
@Override
protected void onStop() {
context.getApplicationContext().unbindService(mConnection);
disposable.clear();
super.onStop();
}
@Override
public void preprocessPreferences(@NonNull PreferenceFragmentCompat preferenceFragment) {
super.preprocessPreferences(preferenceFragment);
if (config.getNSCLIENT()) {
SwitchPreference key_ns_uploadlocalprofile = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_uploadlocalprofile));
if (key_ns_uploadlocalprofile != null) key_ns_uploadlocalprofile.setVisible(false);
SwitchPreference key_ns_autobackfill = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_autobackfill));
if (key_ns_autobackfill != null) key_ns_autobackfill.setVisible(false);
SwitchPreference key_ns_create_announcements_from_errors = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_create_announcements_from_errors));
if (key_ns_create_announcements_from_errors != null)
key_ns_create_announcements_from_errors.setVisible(false);
SwitchPreference key_ns_create_announcements_from_carbs_req = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_create_announcements_from_carbs_req));
if (key_ns_create_announcements_from_carbs_req != null)
key_ns_create_announcements_from_carbs_req.setVisible(false);
SwitchPreference key_ns_upload_only = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_upload_only));
if (key_ns_upload_only != null) {
key_ns_upload_only.setVisible(false);
key_ns_upload_only.setEnabled(false);
}
SwitchPreference key_ns_sync_use_absolute = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_sync_use_absolute));
if (key_ns_sync_use_absolute != null) key_ns_sync_use_absolute.setVisible(false);
} else {
// APS or pumpControl mode
SwitchPreference key_ns_upload_only = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_upload_only));
if (key_ns_upload_only != null)
key_ns_upload_only.setVisible(buildHelper.isEngineeringMode());
}
}
private final ServiceConnection mConnection = new ServiceConnection() {
public void onServiceDisconnected(ComponentName name) {
aapsLogger.debug(LTag.NSCLIENT, "Service is disconnected");
nsClientService = null;
}
public void onServiceConnected(ComponentName name, IBinder service) {
aapsLogger.debug(LTag.NSCLIENT, "Service is connected");
NSClientService.LocalBinder mLocalBinder = (NSClientService.LocalBinder) service;
if (mLocalBinder != null) // is null when running in roboelectric
nsClientService = mLocalBinder.getServiceInstance();
}
};
synchronized void clearLog() {
handler.post(() -> {
synchronized (listLog) {
listLog.clear();
}
rxBus.send(new EventNSClientUpdateGUI());
});
}
private synchronized void addToLog(final EventNSClientNewLog ev) {
handler.post(() -> {
synchronized (listLog) {
listLog.add(ev);
// remove the first line if log is too large
if (listLog.size() >= Constants.MAX_LOG_LINES) {
listLog.remove(0);
}
}
rxBus.send(new EventNSClientUpdateGUI());
});
}
synchronized void updateLog() {
try {
StringBuilder newTextLog = new StringBuilder();
synchronized (listLog) {
for (EventNSClientNewLog log : listLog) {
newTextLog.append(log.toPreparedHtml());
}
}
textLog = HtmlHelper.INSTANCE.fromHtml(newTextLog.toString());
} catch (OutOfMemoryError e) {
ToastUtils.showToastInUiThread(context, rxBus, "Out of memory!\nStop using this phone !!!", R.raw.error);
}
}
void resend(String reason) {
if (nsClientService != null)
nsClientService.resend(reason);
}
public void pause(boolean newState) {
sp.putBoolean(R.string.key_nsclientinternal_paused, newState);
paused = newState;
rxBus.send(new EventPreferenceChange(resourceHelper, R.string.key_nsclientinternal_paused));
}
public String url() {
return NSClientService.nsURL;
}
public boolean hasWritePermission() {
return NSClientService.hasWriteAuth;
}
public void handleClearAlarm(NSAlarm originalAlarm, long silenceTimeInMsec) {
if (!isEnabled(PluginType.GENERAL)) {
return;
}
if (sp.getBoolean(R.string.key_ns_noupload, false)) {
aapsLogger.debug(LTag.NSCLIENT, "Upload disabled. Message dropped");
return;
}
AlarmAck ack = new AlarmAck();
ack.level = originalAlarm.level();
ack.group = originalAlarm.group();
ack.silenceTime = silenceTimeInMsec;
if (nsClientService != null)
nsClientService.sendAlarmAck(ack);
}
public void updateLatestDateReceivedIfNewer(long latestReceived) {
if (nsClientService != null && latestReceived > nsClientService.latestDateInReceivedData)
nsClientService.latestDateInReceivedData = latestReceived;
}
}

View file

@ -0,0 +1,243 @@
package info.nightscout.androidaps.plugins.general.nsclient
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Handler
import android.os.HandlerThread
import android.os.IBinder
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreference
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.EventAppExit
import info.nightscout.androidaps.events.EventChargingState
import info.nightscout.androidaps.events.EventNetworkChange
import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
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.nsclient.data.AlarmAck
import info.nightscout.androidaps.plugins.general.nsclient.data.NSAlarm
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientNewLog
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientResend
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientUpdateGUI
import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientService
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.HtmlHelper.fromHtml
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
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 java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class NSClientPlugin @Inject constructor(
injector: HasAndroidInjector,
aapsLogger: AAPSLogger,
private val aapsSchedulers: AapsSchedulers,
private val rxBus: RxBusWrapper,
resourceHelper: ResourceHelper,
private val context: Context,
private val fabricPrivacy: FabricPrivacy,
private val sp: SP,
private val nsClientReceiverDelegate: NsClientReceiverDelegate,
private val config: Config,
private val buildHelper: BuildHelper
) : PluginBase(PluginDescription()
.mainType(PluginType.GENERAL)
.fragmentClass(NSClientFragment::class.java.name)
.pluginIcon(R.drawable.ic_nightscout_syncs)
.pluginName(R.string.nsclientinternal)
.shortName(R.string.nsclientinternal_shortname)
.preferencesId(R.xml.pref_nsclientinternal)
.description(R.string.description_ns_client),
aapsLogger, resourceHelper, injector
) {
private val disposable = CompositeDisposable()
var handler: Handler? = null
private val listLog: MutableList<EventNSClientNewLog> = ArrayList()
var textLog = fromHtml("")
var paused = false
var autoscroll = false
var status = ""
var nsClientService: NSClientService? = null
val isAllowed: Boolean
get() = nsClientReceiverDelegate.allowed
init {
if (config.NSCLIENT) {
pluginDescription.alwaysEnabled(true).visibleByDefault(true)
}
if (handler == null) {
val handlerThread = HandlerThread(NSClientPlugin::class.java.simpleName + "Handler")
handlerThread.start()
handler = Handler(handlerThread.looper)
}
}
override fun onStart() {
paused = sp.getBoolean(R.string.key_nsclientinternal_paused, false)
autoscroll = sp.getBoolean(R.string.key_nsclientinternal_autoscroll, true)
val intent = Intent(context, NSClientService::class.java)
context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
super.onStart()
nsClientReceiverDelegate.grabReceiversState()
disposable.add(rxBus
.toObservable(EventNSClientStatus::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ event: EventNSClientStatus ->
status = event.getStatus(resourceHelper)
rxBus.send(EventNSClientUpdateGUI())
}, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventNetworkChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ ev -> nsClientReceiverDelegate.onStatusEvent(ev) }, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventPreferenceChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ ev -> nsClientReceiverDelegate.onStatusEvent(ev) }, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventAppExit::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ if (nsClientService != null) context.unbindService(mConnection) }, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventNSClientNewLog::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ event: EventNSClientNewLog ->
addToLog(event)
aapsLogger.debug(LTag.NSCLIENT, event.action + " " + event.logText)
}, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventChargingState::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ ev -> nsClientReceiverDelegate.onStatusEvent(ev) }, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventNSClientResend::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ event -> resend(event.reason) }, fabricPrivacy::logException)
)
}
override fun onStop() {
context.applicationContext.unbindService(mConnection)
disposable.clear()
super.onStop()
}
override fun preprocessPreferences(preferenceFragment: PreferenceFragmentCompat) {
super.preprocessPreferences(preferenceFragment)
if (config.NSCLIENT) {
preferenceFragment.findPreference<PreferenceScreen>(resourceHelper.gs(R.string.ns_sync_options))?.isVisible = false
preferenceFragment.findPreference<SwitchPreference>(resourceHelper.gs(R.string.key_ns_create_announcements_from_errors))?.isVisible = false
preferenceFragment.findPreference<SwitchPreference>(resourceHelper.gs(R.string.key_ns_create_announcements_from_carbs_req))?.isVisible = false
preferenceFragment.findPreference<SwitchPreference>(resourceHelper.gs(R.string.key_ns_sync_use_absolute))?.isVisible = false
} else {
// APS or pumpControl mode
preferenceFragment.findPreference<SwitchPreference>(resourceHelper.gs(R.string.key_ns_receive_profile_switch))?.isVisible = buildHelper.isEngineeringMode()
preferenceFragment.findPreference<SwitchPreference>(resourceHelper.gs(R.string.key_ns_receive_insulin))?.isVisible = buildHelper.isEngineeringMode()
preferenceFragment.findPreference<SwitchPreference>(resourceHelper.gs(R.string.key_ns_receive_carbs))?.isVisible = buildHelper.isEngineeringMode()
preferenceFragment.findPreference<SwitchPreference>(resourceHelper.gs(R.string.key_ns_receive_temp_target))?.isVisible = buildHelper.isEngineeringMode()
}
}
private val mConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName) {
aapsLogger.debug(LTag.NSCLIENT, "Service is disconnected")
nsClientService = null
}
override fun onServiceConnected(name: ComponentName, service: IBinder) {
aapsLogger.debug(LTag.NSCLIENT, "Service is connected")
val mLocalBinder = service as NSClientService.LocalBinder
@Suppress("UNNECESSARY_SAFE_CALL")
nsClientService = mLocalBinder?.serviceInstance // is null when running in roboelectric
}
}
@Synchronized fun clearLog() {
handler?.post {
synchronized(listLog) { listLog.clear() }
rxBus.send(EventNSClientUpdateGUI())
}
}
@Synchronized private fun addToLog(ev: EventNSClientNewLog) {
handler?.post {
synchronized(listLog) {
listLog.add(ev)
// remove the first line if log is too large
if (listLog.size >= Constants.MAX_LOG_LINES) {
listLog.removeAt(0)
}
}
rxBus.send(EventNSClientUpdateGUI())
}
}
@Synchronized fun updateLog() {
try {
val newTextLog = StringBuilder()
synchronized(listLog) {
for (log in listLog) {
newTextLog.append(log.toPreparedHtml())
}
}
textLog = fromHtml(newTextLog.toString())
} catch (e: OutOfMemoryError) {
ToastUtils.showToastInUiThread(context, rxBus, "Out of memory!\nStop using this phone !!!", R.raw.error)
}
}
fun resend(reason: String) {
nsClientService?.resend(reason)
}
fun pause(newState: Boolean) {
sp.putBoolean(R.string.key_nsclientinternal_paused, newState)
paused = newState
rxBus.send(EventPreferenceChange(resourceHelper, R.string.key_nsclientinternal_paused))
}
fun url(): String = nsClientService?.nsURL ?: ""
fun hasWritePermission(): Boolean = nsClientService?.hasWriteAuth ?: false
fun handleClearAlarm(originalAlarm: NSAlarm, silenceTimeInMilliseconds: Long) {
if (!isEnabled(PluginType.GENERAL)) return
if (!sp.getBoolean(R.string.key_ns_upload, false)) {
aapsLogger.debug(LTag.NSCLIENT, "Upload disabled. Message dropped")
return
}
nsClientService?.sendAlarmAck(
AlarmAck().also { ack ->
ack.level = originalAlarm.level()
ack.group = originalAlarm.group()
ack.silenceTime = silenceTimeInMilliseconds
})
}
fun updateLatestDateReceivedIfNewer(latestReceived: Long) {
nsClientService?.let { if (latestReceived > it.latestDateInReceivedData) it.latestDateInReceivedData = latestReceived }
}
}

View file

@ -42,8 +42,9 @@ class NSClientRemoveWorker(
@Inject lateinit var uel: UserEntryLogger @Inject lateinit var uel: UserEntryLogger
override fun doWork(): Result { override fun doWork(): Result {
val acceptNSData = !sp.getBoolean(R.string.key_ns_upload_only, true) && buildHelper.isEngineeringMode() || config.NSCLIENT // Do not accept removed data over WS. Only invalidated trough NSClient
if (!acceptNSData) return Result.success() @Suppress("ConstantConditionIf")
if (true) return Result.success()
var ret = Result.success() var ret = Result.success()

View file

@ -39,7 +39,7 @@ class NSClientUpdateRemoveAckWorker(
is PairTemporaryTarget -> { is PairTemporaryTarget -> {
val pair = ack.originalObject val pair = ack.originalObject
dataSyncSelector.confirmLastTempTargetsIdIfGreater(pair.updateRecordId) dataSyncSelector.confirmLastTempTargetsIdIfGreater(pair.updateRecordId)
rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked TemporaryTarget" + ack._id)) rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked TemporaryTarget" + ack._id))
// Send new if waiting // Send new if waiting
dataSyncSelector.processChangedTempTargetsCompat() dataSyncSelector.processChangedTempTargetsCompat()
ret = Result.success(workDataOf("ProcessedData" to pair.toString())) ret = Result.success(workDataOf("ProcessedData" to pair.toString()))
@ -48,7 +48,7 @@ class NSClientUpdateRemoveAckWorker(
is PairGlucoseValue -> { is PairGlucoseValue -> {
val pair = ack.originalObject val pair = ack.originalObject
dataSyncSelector.confirmLastGlucoseValueIdIfGreater(pair.updateRecordId) dataSyncSelector.confirmLastGlucoseValueIdIfGreater(pair.updateRecordId)
rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked GlucoseValue " + ack._id)) rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked GlucoseValue " + ack._id))
// Send new if waiting // Send new if waiting
dataSyncSelector.processChangedGlucoseValuesCompat() dataSyncSelector.processChangedGlucoseValuesCompat()
ret = Result.success(workDataOf("ProcessedData" to pair.toString())) ret = Result.success(workDataOf("ProcessedData" to pair.toString()))
@ -57,7 +57,7 @@ class NSClientUpdateRemoveAckWorker(
is PairFood -> { is PairFood -> {
val pair = ack.originalObject val pair = ack.originalObject
dataSyncSelector.confirmLastFoodIdIfGreater(pair.updateRecordId) dataSyncSelector.confirmLastFoodIdIfGreater(pair.updateRecordId)
rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked Food " + ack._id)) rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked Food " + ack._id))
// Send new if waiting // Send new if waiting
dataSyncSelector.processChangedFoodsCompat() dataSyncSelector.processChangedFoodsCompat()
ret = Result.success(workDataOf("ProcessedData" to pair.toString())) ret = Result.success(workDataOf("ProcessedData" to pair.toString()))
@ -66,7 +66,7 @@ class NSClientUpdateRemoveAckWorker(
is PairTherapyEvent -> { is PairTherapyEvent -> {
val pair = ack.originalObject val pair = ack.originalObject
dataSyncSelector.confirmLastTherapyEventIdIfGreater(pair.updateRecordId) dataSyncSelector.confirmLastTherapyEventIdIfGreater(pair.updateRecordId)
rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked TherapyEvent " + ack._id)) rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked TherapyEvent " + ack._id))
// Send new if waiting // Send new if waiting
dataSyncSelector.processChangedTherapyEventsCompat() dataSyncSelector.processChangedTherapyEventsCompat()
ret = Result.success(workDataOf("ProcessedData" to pair.toString())) ret = Result.success(workDataOf("ProcessedData" to pair.toString()))
@ -75,7 +75,7 @@ class NSClientUpdateRemoveAckWorker(
is PairBolus -> { is PairBolus -> {
val pair = ack.originalObject val pair = ack.originalObject
dataSyncSelector.confirmLastBolusIdIfGreater(pair.updateRecordId) dataSyncSelector.confirmLastBolusIdIfGreater(pair.updateRecordId)
rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked Bolus " + ack._id)) rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked Bolus " + ack._id))
// Send new if waiting // Send new if waiting
dataSyncSelector.processChangedBolusesCompat() dataSyncSelector.processChangedBolusesCompat()
ret = Result.success(workDataOf("ProcessedData" to pair.toString())) ret = Result.success(workDataOf("ProcessedData" to pair.toString()))
@ -84,7 +84,7 @@ class NSClientUpdateRemoveAckWorker(
is PairCarbs -> { is PairCarbs -> {
val pair = ack.originalObject val pair = ack.originalObject
dataSyncSelector.confirmLastCarbsIdIfGreater(pair.updateRecordId) dataSyncSelector.confirmLastCarbsIdIfGreater(pair.updateRecordId)
rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked Carbs " + ack._id)) rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked Carbs " + ack._id))
// Send new if waiting // Send new if waiting
dataSyncSelector.processChangedCarbsCompat() dataSyncSelector.processChangedCarbsCompat()
ret = Result.success(workDataOf("ProcessedData" to pair.toString())) ret = Result.success(workDataOf("ProcessedData" to pair.toString()))
@ -93,7 +93,7 @@ class NSClientUpdateRemoveAckWorker(
is PairBolusCalculatorResult -> { is PairBolusCalculatorResult -> {
val pair = ack.originalObject val pair = ack.originalObject
dataSyncSelector.confirmLastBolusCalculatorResultsIdIfGreater(pair.updateRecordId) dataSyncSelector.confirmLastBolusCalculatorResultsIdIfGreater(pair.updateRecordId)
rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked BolusCalculatorResult " + ack._id)) rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked BolusCalculatorResult " + ack._id))
// Send new if waiting // Send new if waiting
dataSyncSelector.processChangedBolusCalculatorResultsCompat() dataSyncSelector.processChangedBolusCalculatorResultsCompat()
ret = Result.success(workDataOf("ProcessedData" to pair.toString())) ret = Result.success(workDataOf("ProcessedData" to pair.toString()))
@ -102,7 +102,7 @@ class NSClientUpdateRemoveAckWorker(
is PairTemporaryBasal -> { is PairTemporaryBasal -> {
val pair = ack.originalObject val pair = ack.originalObject
dataSyncSelector.confirmLastTemporaryBasalIdIfGreater(pair.updateRecordId) dataSyncSelector.confirmLastTemporaryBasalIdIfGreater(pair.updateRecordId)
rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked TemporaryBasal " + ack._id)) rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked TemporaryBasal " + ack._id))
// Send new if waiting // Send new if waiting
dataSyncSelector.processChangedTemporaryBasalsCompat() dataSyncSelector.processChangedTemporaryBasalsCompat()
ret = Result.success(workDataOf("ProcessedData" to pair.toString())) ret = Result.success(workDataOf("ProcessedData" to pair.toString()))
@ -111,7 +111,7 @@ class NSClientUpdateRemoveAckWorker(
is PairExtendedBolus -> { is PairExtendedBolus -> {
val pair = ack.originalObject val pair = ack.originalObject
dataSyncSelector.confirmLastExtendedBolusIdIfGreater(pair.updateRecordId) dataSyncSelector.confirmLastExtendedBolusIdIfGreater(pair.updateRecordId)
rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked ExtendedBolus " + ack._id)) rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked ExtendedBolus " + ack._id))
// Send new if waiting // Send new if waiting
dataSyncSelector.processChangedExtendedBolusesCompat() dataSyncSelector.processChangedExtendedBolusesCompat()
ret = Result.success(workDataOf("ProcessedData" to pair.toString())) ret = Result.success(workDataOf("ProcessedData" to pair.toString()))
@ -120,11 +120,20 @@ class NSClientUpdateRemoveAckWorker(
is PairProfileSwitch -> { is PairProfileSwitch -> {
val pair = ack.originalObject val pair = ack.originalObject
dataSyncSelector.confirmLastProfileSwitchIdIfGreater(pair.updateRecordId) dataSyncSelector.confirmLastProfileSwitchIdIfGreater(pair.updateRecordId)
rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked ProfileSwitch " + ack._id)) rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked ProfileSwitch " + ack._id))
// Send new if waiting // Send new if waiting
dataSyncSelector.processChangedProfileSwitchesCompat() dataSyncSelector.processChangedProfileSwitchesCompat()
ret = Result.success(workDataOf("ProcessedData" to pair.toString())) ret = Result.success(workDataOf("ProcessedData" to pair.toString()))
} }
is PairOfflineEvent -> {
val pair = ack.originalObject
dataSyncSelector.confirmLastOfflineEventIdIfGreater(pair.updateRecordId)
rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked OfflineEvent" + ack._id))
// Send new if waiting
dataSyncSelector.processChangedOfflineEventsCompat()
ret = Result.success(workDataOf("ProcessedData" to pair.toString()))
}
} }
return ret return ret
} }

View file

@ -1,117 +0,0 @@
package info.nightscout.androidaps.plugins.general.nsclient;
import java.util.Arrays;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.events.EventChargingState;
import info.nightscout.androidaps.events.EventNetworkChange;
import info.nightscout.androidaps.events.EventPreferenceChange;
import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
import info.nightscout.androidaps.receivers.ReceiverStatusStore;
import info.nightscout.androidaps.utils.resources.ResourceHelper;
import info.nightscout.androidaps.utils.sharedPreferences.SP;
@Singleton
class NsClientReceiverDelegate {
private boolean allowedChargingState = true;
private boolean allowedNetworkState = true;
boolean allowed = true;
private RxBusWrapper rxBus;
private ResourceHelper resourceHelper;
private SP sp;
private ReceiverStatusStore receiverStatusStore;
@Inject
public NsClientReceiverDelegate(
RxBusWrapper rxBus,
ResourceHelper resourceHelper,
SP sp,
ReceiverStatusStore receiverStatusStore
) {
this.rxBus = rxBus;
this.resourceHelper = resourceHelper;
this.sp = sp;
this.receiverStatusStore = receiverStatusStore;
}
void grabReceiversState() {
receiverStatusStore.updateNetworkStatus();
}
void onStatusEvent(EventPreferenceChange ev) {
if (ev.isChanged(resourceHelper, R.string.key_ns_wifionly) ||
ev.isChanged(resourceHelper, R.string.key_ns_wifi_ssids) ||
ev.isChanged(resourceHelper, R.string.key_ns_allowroaming)
) {
receiverStatusStore.updateNetworkStatus();
onStatusEvent(receiverStatusStore.getLastNetworkEvent());
} else if (ev.isChanged(resourceHelper, R.string.key_ns_chargingonly)) {
receiverStatusStore.broadcastChargingState();
}
}
void onStatusEvent(final EventChargingState ev) {
boolean newChargingState = calculateStatus(ev);
if (newChargingState != allowedChargingState) {
allowedChargingState = newChargingState;
processStateChange();
}
}
void onStatusEvent(final EventNetworkChange ev) {
boolean newNetworkState = calculateStatus(ev);
if (newNetworkState != allowedNetworkState) {
allowedNetworkState = newNetworkState;
processStateChange();
}
}
private void processStateChange() {
boolean newAllowedState = allowedChargingState && allowedNetworkState;
if (newAllowedState != allowed) {
allowed = newAllowedState;
rxBus.send(new EventPreferenceChange(resourceHelper.gs(R.string.key_nsclientinternal_paused)));
}
}
boolean calculateStatus(final EventChargingState ev) {
boolean chargingOnly = sp.getBoolean(R.string.key_ns_chargingonly, false);
boolean newAllowedState = true;
if (!ev.isCharging() && chargingOnly) {
newAllowedState = false;
}
return newAllowedState;
}
boolean calculateStatus(final EventNetworkChange ev) {
boolean wifiOnly = sp.getBoolean(R.string.key_ns_wifionly, false);
String allowedSSIDstring = sp.getString(R.string.key_ns_wifi_ssids, "");
String[] allowedSSIDs = allowedSSIDstring.split(";");
if (allowedSSIDstring.isEmpty()) allowedSSIDs = new String[0];
boolean allowRoaming = sp.getBoolean(R.string.key_ns_allowroaming, true);
boolean newAllowedState = true;
if (ev.getWifiConnected()) {
if (allowedSSIDs.length != 0 && !Arrays.asList(allowedSSIDs).contains(ev.getSsid())) {
newAllowedState = false;
}
} else {
if ((!allowRoaming && ev.getRoaming()) || wifiOnly) {
newAllowedState = false;
}
}
return newAllowedState;
}
}

View file

@ -0,0 +1,91 @@
package info.nightscout.androidaps.plugins.general.nsclient
import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.EventChargingState
import info.nightscout.androidaps.events.EventNetworkChange
import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.receivers.ReceiverStatusStore
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class NsClientReceiverDelegate @Inject constructor(
private val rxBus: RxBusWrapper,
private val resourceHelper: ResourceHelper,
private val sp: SP,
private val receiverStatusStore: ReceiverStatusStore
) {
private var allowedChargingState = true
private var allowedNetworkState = true
var allowed = true
fun grabReceiversState() {
receiverStatusStore.updateNetworkStatus()
}
fun onStatusEvent(ev: EventPreferenceChange) {
if (ev.isChanged(resourceHelper, R.string.key_ns_wifionly) ||
ev.isChanged(resourceHelper, R.string.key_ns_wifi_ssids) ||
ev.isChanged(resourceHelper, R.string.key_ns_allowroaming)) {
receiverStatusStore.updateNetworkStatus()
onStatusEvent(receiverStatusStore.lastNetworkEvent)
} else if (ev.isChanged(resourceHelper, R.string.key_ns_chargingonly)) {
receiverStatusStore.broadcastChargingState()
}
}
fun onStatusEvent(ev: EventChargingState) {
val newChargingState = calculateStatus(ev)
if (newChargingState != allowedChargingState) {
allowedChargingState = newChargingState
processStateChange()
}
}
fun onStatusEvent(ev: EventNetworkChange?) {
val newNetworkState = calculateStatus(ev)
if (newNetworkState != allowedNetworkState) {
allowedNetworkState = newNetworkState
processStateChange()
}
}
private fun processStateChange() {
val newAllowedState = allowedChargingState && allowedNetworkState
if (newAllowedState != allowed) {
allowed = newAllowedState
rxBus.send(EventPreferenceChange(resourceHelper.gs(R.string.key_nsclientinternal_paused)))
}
}
fun calculateStatus(ev: EventChargingState): Boolean {
val chargingOnly = sp.getBoolean(R.string.key_ns_chargingonly, false)
var newAllowedState = true
if (!ev.isCharging && chargingOnly) {
newAllowedState = false
}
return newAllowedState
}
fun calculateStatus(ev: EventNetworkChange?): Boolean {
val wifiOnly = sp.getBoolean(R.string.key_ns_wifionly, false)
val allowedSsidString = sp.getString(R.string.key_ns_wifi_ssids, "")
val allowedSSIDs: List<String> = if (allowedSsidString.isEmpty()) List(0) { "" } else allowedSsidString.split(";")
val allowRoaming = sp.getBoolean(R.string.key_ns_allowroaming, true)
var newAllowedState = true
if (ev?.wifiConnected == true) {
if (allowedSSIDs.isNotEmpty() && !allowedSSIDs.contains(ev.ssid)) {
newAllowedState = false
}
} else {
if (!allowRoaming && ev?.roaming == true || wifiOnly) {
newAllowedState = false
}
}
return newAllowedState
}
}

View file

@ -1,124 +0,0 @@
package info.nightscout.androidaps.plugins.general.nsclient;
import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;
import com.j256.ormlite.dao.CloseableIterator;
import org.json.JSONException;
import org.json.JSONObject;
import java.sql.SQLException;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.db.DatabaseHelper;
import info.nightscout.androidaps.db.DbRequest;
import info.nightscout.androidaps.interfaces.DatabaseHelperInterface;
import info.nightscout.androidaps.interfaces.UploadQueueAdminInterface;
import info.nightscout.androidaps.interfaces.UploadQueueInterface;
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.nsclient.events.EventNSClientResend;
import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientService;
import info.nightscout.androidaps.utils.sharedPreferences.SP;
/**
* Created by mike on 21.02.2016.
*/
public class UploadQueue implements UploadQueueAdminInterface {
private final AAPSLogger aapsLogger;
private final DatabaseHelperInterface databaseHelper;
private final Context context;
private final SP sp;
private final RxBusWrapper rxBus;
public UploadQueue(
AAPSLogger aapsLogger,
DatabaseHelperInterface databaseHelper,
Context context,
SP sp,
RxBusWrapper rxBus
) {
this.aapsLogger = aapsLogger;
this.databaseHelper = databaseHelper;
this.context = context;
this.sp = sp;
this.rxBus = rxBus;
}
public String status() {
return "QUEUE: " + databaseHelper.size(DatabaseHelper.DATABASE_DBREQUESTS);
}
@Override
public long size() {
return databaseHelper.size(DatabaseHelper.DATABASE_DBREQUESTS);
}
private void startService() {
if (NSClientService.handler == null) {
context.startService(new Intent(context, NSClientService.class));
SystemClock.sleep(2000);
}
}
public void add(final DbRequest dbr) {
if (sp.getBoolean(R.string.key_ns_noupload, false)) return;
aapsLogger.debug(LTag.NSCLIENT, "Adding to queue: " + dbr.log());
try {
databaseHelper.create(dbr);
} catch (Exception e) {
aapsLogger.error("Unhandled exception", e);
}
rxBus.send(new EventNSClientResend("newdata"));
}
@Override public void clearQueue() {
startService();
if (NSClientService.handler != null) {
NSClientService.handler.post(() -> {
aapsLogger.debug(LTag.NSCLIENT, "ClearQueue");
databaseHelper.deleteAllDbRequests();
aapsLogger.debug(LTag.NSCLIENT, status());
});
}
}
@Override
public void removeByMongoId(final String action, final String _id) {
if (_id == null || _id.equals(""))
return;
startService();
if (NSClientService.handler != null) {
NSClientService.handler.post(() -> {
databaseHelper.deleteDbRequestbyMongoId(action, _id);
aapsLogger.debug(LTag.NSCLIENT, "Removing " + _id + " from UploadQueue. " + status());
});
}
}
@Override public String textList() {
String result = "";
CloseableIterator<DbRequest> iterator;
try {
iterator = databaseHelper.getDbRequestIterator();
try {
while (iterator.hasNext()) {
DbRequest dbr = iterator.next();
result += "<br>";
result += dbr.action.toUpperCase() + " ";
result += dbr.collection + ": ";
result += dbr.data;
}
} finally {
iterator.close();
}
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return result;
}
}

View file

@ -1,913 +0,0 @@
package info.nightscout.androidaps.plugins.general.nsclient.services;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.SystemClock;
import androidx.work.OneTimeWorkRequest;
import com.google.common.base.Charsets;
import com.google.common.hash.Hashing;
import com.j256.ormlite.dao.CloseableIterator;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.URISyntaxException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import dagger.android.DaggerService;
import dagger.android.HasAndroidInjector;
import info.nightscout.androidaps.interfaces.Config;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.database.AppRepository;
import info.nightscout.androidaps.db.DbRequest;
import info.nightscout.androidaps.events.EventAppExit;
import info.nightscout.androidaps.events.EventConfigBuilderChange;
import info.nightscout.androidaps.events.EventPreferenceChange;
import info.nightscout.androidaps.interfaces.DataSyncSelector;
import info.nightscout.androidaps.interfaces.DatabaseHelperInterface;
import info.nightscout.androidaps.interfaces.PluginType;
import info.nightscout.androidaps.interfaces.UploadQueueInterface;
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.food.FoodPlugin;
import info.nightscout.androidaps.plugins.general.nsclient.NSClientAddAckWorker;
import info.nightscout.androidaps.plugins.general.nsclient.NSClientAddUpdateWorker;
import info.nightscout.androidaps.plugins.general.nsclient.NSClientMbgWorker;
import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin;
import info.nightscout.androidaps.plugins.general.nsclient.NSClientRemoveWorker;
import info.nightscout.androidaps.plugins.general.nsclient.NSClientUpdateRemoveAckWorker;
import info.nightscout.androidaps.plugins.general.nsclient.acks.NSAddAck;
import info.nightscout.androidaps.plugins.general.nsclient.acks.NSAuthAck;
import info.nightscout.androidaps.plugins.general.nsclient.acks.NSUpdateAck;
import info.nightscout.androidaps.plugins.general.nsclient.data.AlarmAck;
import info.nightscout.androidaps.plugins.general.nsclient.data.NSAlarm;
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus;
import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus;
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientNewLog;
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart;
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus;
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientUpdateGUI;
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.notifications.Notification;
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationWithAction;
import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin;
import info.nightscout.androidaps.plugins.source.NSClientSourcePlugin;
import info.nightscout.androidaps.receivers.DataWorker;
import info.nightscout.androidaps.services.Intents;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.FabricPrivacy;
import info.nightscout.androidaps.utils.JsonHelper;
import info.nightscout.androidaps.utils.T;
import info.nightscout.androidaps.utils.buildHelper.BuildHelper;
import info.nightscout.androidaps.utils.resources.ResourceHelper;
import info.nightscout.androidaps.utils.rx.AapsSchedulers;
import info.nightscout.androidaps.utils.sharedPreferences.SP;
import io.reactivex.disposables.CompositeDisposable;
import io.socket.client.IO;
import io.socket.client.Socket;
import io.socket.emitter.Emitter;
public class NSClientService extends DaggerService {
@Inject HasAndroidInjector injector;
@Inject AAPSLogger aapsLogger;
@Inject AapsSchedulers aapsSchedulers;
@Inject NSSettingsStatus nsSettingsStatus;
@Inject NSDeviceStatus nsDeviceStatus;
@Inject DatabaseHelperInterface databaseHelper;
@Inject RxBusWrapper rxBus;
@Inject ResourceHelper resourceHelper;
@Inject SP sp;
@Inject FabricPrivacy fabricPrivacy;
@Inject NSClientPlugin nsClientPlugin;
@Inject BuildHelper buildHelper;
@Inject Config config;
@Inject DateUtil dateUtil;
@Inject UploadQueueInterface uploadQueue;
@Inject DataWorker dataWorker;
@Inject DataSyncSelector dataSyncSelector;
@Inject AppRepository repository;
private final CompositeDisposable disposable = new CompositeDisposable();
static public PowerManager.WakeLock mWakeLock;
private final IBinder mBinder = new NSClientService.LocalBinder();
static public Handler handler;
public static Socket mSocket;
public static boolean isConnected = false;
public static boolean hasWriteAuth = false;
private static Integer dataCounter = 0;
private static Integer connectCounter = 0;
private boolean nsEnabled = false;
static public String nsURL = "";
private String nsAPISecret = "";
private String nsDevice = "";
private final Integer nsHours = 48;
public long lastResendTime = 0;
public long lastAckTime = 0;
public long latestDateInReceivedData = 0;
private String nsAPIhashCode = "";
private final ArrayList<Long> reconnections = new ArrayList<>();
private final int WATCHDOG_INTERVAL_MINUTES = 2;
private final int WATCHDOG_RECONNECT_IN = 15;
private final int WATCHDOG_MAX_CONNECTIONS = 5;
public NSClientService() {
super();
if (handler == null) {
HandlerThread handlerThread = new HandlerThread(NSClientService.class.getSimpleName() + "Handler");
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
}
}
@Override
public void onCreate() {
super.onCreate();
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AndroidAPS:NSClientService");
mWakeLock.acquire();
initialize();
disposable.add(rxBus
.toObservable(EventConfigBuilderChange.class)
.observeOn(aapsSchedulers.getIo())
.subscribe(event -> {
if (nsEnabled != nsClientPlugin.isEnabled(PluginType.GENERAL)) {
latestDateInReceivedData = 0;
destroy();
initialize();
}
}, fabricPrivacy::logException)
);
disposable.add(rxBus
.toObservable(EventPreferenceChange.class)
.observeOn(aapsSchedulers.getIo())
.subscribe(event -> {
if (event.isChanged(resourceHelper, R.string.key_nsclientinternal_url) ||
event.isChanged(resourceHelper, R.string.key_nsclientinternal_api_secret) ||
event.isChanged(resourceHelper, R.string.key_nsclientinternal_paused)
) {
latestDateInReceivedData = 0;
destroy();
initialize();
}
}, fabricPrivacy::logException)
);
disposable.add(rxBus
.toObservable(EventAppExit.class)
.observeOn(aapsSchedulers.getIo())
.subscribe(event -> {
aapsLogger.debug(LTag.NSCLIENT, "EventAppExit received");
destroy();
stopSelf();
}, fabricPrivacy::logException)
);
disposable.add(rxBus
.toObservable(EventNSClientRestart.class)
.observeOn(aapsSchedulers.getIo())
.subscribe(event -> {
latestDateInReceivedData = 0;
restart();
}, fabricPrivacy::logException)
);
disposable.add(rxBus
.toObservable(NSAuthAck.class)
.observeOn(aapsSchedulers.getIo())
.subscribe(this::processAuthAck, fabricPrivacy::logException)
);
disposable.add(rxBus
.toObservable(NSUpdateAck.class)
.observeOn(aapsSchedulers.getIo())
.subscribe(this::processUpdateAck, fabricPrivacy::logException)
);
disposable.add(rxBus
.toObservable(NSAddAck.class)
.observeOn(aapsSchedulers.getIo())
.subscribe(this::processAddAck, fabricPrivacy::logException)
);
}
@Override
public void onDestroy() {
super.onDestroy();
disposable.clear();
if (mWakeLock.isHeld()) mWakeLock.release();
}
public void processAddAck(NSAddAck ack) {
lastAckTime = dateUtil.now();
dataWorker.enqueue(
new OneTimeWorkRequest.Builder(NSClientAddAckWorker.class)
.setInputData(dataWorker.storeInputData(ack, null))
.build());
}
public void processUpdateAck(NSUpdateAck ack) {
lastAckTime = dateUtil.now();
dataWorker.enqueue(
new OneTimeWorkRequest.Builder(NSClientUpdateRemoveAckWorker.class)
.setInputData(dataWorker.storeInputData(ack, null))
.build());
}
public void processAuthAck(NSAuthAck ack) {
String connectionStatus = "Authenticated (";
if (ack.read) connectionStatus += "R";
if (ack.write) connectionStatus += "W";
if (ack.write_treatment) connectionStatus += "T";
connectionStatus += ')';
isConnected = true;
hasWriteAuth = ack.write && ack.write_treatment;
rxBus.send(new EventNSClientStatus(connectionStatus));
rxBus.send(new EventNSClientNewLog("AUTH", connectionStatus));
if (!ack.write) {
rxBus.send(new EventNSClientNewLog("ERROR", "Write permission not granted !!!!"));
}
if (!ack.write_treatment) {
rxBus.send(new EventNSClientNewLog("ERROR", "Write treatment permission not granted !!!!"));
}
if (!hasWriteAuth) {
Notification noperm = new Notification(Notification.NSCLIENT_NO_WRITE_PERMISSION, resourceHelper.gs(R.string.nowritepermission), Notification.URGENT);
rxBus.send(new EventNewNotification(noperm));
} else {
rxBus.send(new EventDismissNotification(Notification.NSCLIENT_NO_WRITE_PERMISSION));
}
}
public class LocalBinder extends Binder {
public NSClientService getServiceInstance() {
return NSClientService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
@SuppressWarnings("deprecation")
public void initialize() {
dataCounter = 0;
readPreferences();
if (!nsAPISecret.equals(""))
nsAPIhashCode = Hashing.sha1().hashString(nsAPISecret, Charsets.UTF_8).toString();
rxBus.send(new EventNSClientStatus("Initializing"));
if (!nsClientPlugin.isAllowed()) {
rxBus.send(new EventNSClientNewLog("NSCLIENT", "not allowed"));
rxBus.send(new EventNSClientStatus("Not allowed"));
} else if (nsClientPlugin.paused) {
rxBus.send(new EventNSClientNewLog("NSCLIENT", "paused"));
rxBus.send(new EventNSClientStatus("Paused"));
} else if (!nsEnabled) {
rxBus.send(new EventNSClientNewLog("NSCLIENT", "disabled"));
rxBus.send(new EventNSClientStatus("Disabled"));
} else if (!nsURL.equals("") && (buildHelper.isEngineeringMode() || nsURL.toLowerCase().startsWith("https://"))) {
try {
rxBus.send(new EventNSClientStatus("Connecting ..."));
IO.Options opt = new IO.Options();
opt.forceNew = true;
opt.reconnection = true;
mSocket = IO.socket(nsURL, opt);
mSocket.on(Socket.EVENT_CONNECT, onConnect);
mSocket.on(Socket.EVENT_DISCONNECT, onDisconnect);
mSocket.on(Socket.EVENT_ERROR, onError);
mSocket.on(Socket.EVENT_CONNECT_ERROR, onError);
mSocket.on(Socket.EVENT_CONNECT_TIMEOUT, onError);
mSocket.on(Socket.EVENT_PING, onPing);
rxBus.send(new EventNSClientNewLog("NSCLIENT", "do connect"));
mSocket.connect();
mSocket.on("dataUpdate", onDataUpdate);
mSocket.on("announcement", onAnnouncement);
mSocket.on("alarm", onAlarm);
mSocket.on("urgent_alarm", onUrgentAlarm);
mSocket.on("clear_alarm", onClearAlarm);
} catch (URISyntaxException | RuntimeException e) {
rxBus.send(new EventNSClientNewLog("NSCLIENT", "Wrong URL syntax"));
rxBus.send(new EventNSClientStatus("Wrong URL syntax"));
}
} else if (nsURL.toLowerCase().startsWith("http://")) {
rxBus.send(new EventNSClientNewLog("NSCLIENT", "NS URL not encrypted"));
rxBus.send(new EventNSClientStatus("Not encrypted"));
} else {
rxBus.send(new EventNSClientNewLog("NSCLIENT", "No NS URL specified"));
rxBus.send(new EventNSClientStatus("Not configured"));
}
}
private final Emitter.Listener onConnect = new Emitter.Listener() {
@Override
public void call(Object... args) {
connectCounter++;
String socketId = mSocket != null ? mSocket.id() : "NULL";
rxBus.send(new EventNSClientNewLog("NSCLIENT", "connect #" + connectCounter + " event. ID: " + socketId));
if (mSocket != null)
sendAuthMessage(new NSAuthAck(rxBus));
watchdog();
}
};
void watchdog() {
synchronized (reconnections) {
long now = dateUtil.now();
reconnections.add(now);
for (int i = 0; i < reconnections.size(); i++) {
Long r = reconnections.get(i);
if (r < now - T.mins(WATCHDOG_INTERVAL_MINUTES).msecs()) {
reconnections.remove(r);
}
}
rxBus.send(new EventNSClientNewLog("WATCHDOG", "connections in last " + WATCHDOG_INTERVAL_MINUTES + " mins: " + reconnections.size() + "/" + WATCHDOG_MAX_CONNECTIONS));
if (reconnections.size() >= WATCHDOG_MAX_CONNECTIONS) {
Notification n = new Notification(Notification.NS_MALFUNCTION, resourceHelper.gs(R.string.nsmalfunction), Notification.URGENT);
rxBus.send(new EventNewNotification(n));
rxBus.send(new EventNSClientNewLog("WATCHDOG", "pausing for " + WATCHDOG_RECONNECT_IN + " mins"));
nsClientPlugin.pause(true);
rxBus.send(new EventNSClientUpdateGUI());
new Thread(() -> {
SystemClock.sleep(T.mins(WATCHDOG_RECONNECT_IN).msecs());
rxBus.send(new EventNSClientNewLog("WATCHDOG", "reenabling NSClient"));
nsClientPlugin.pause(false);
}).start();
}
}
}
private final Emitter.Listener onDisconnect = new Emitter.Listener() {
@Override
public void call(Object... args) {
aapsLogger.debug(LTag.NSCLIENT, "disconnect reason: {}", args);
rxBus.send(new EventNSClientNewLog("NSCLIENT", "disconnect event"));
}
};
public synchronized void destroy() {
if (mSocket != null) {
mSocket.off(Socket.EVENT_CONNECT);
mSocket.off(Socket.EVENT_DISCONNECT);
mSocket.off(Socket.EVENT_PING);
mSocket.off("dataUpdate");
mSocket.off("announcement");
mSocket.off("alarm");
mSocket.off("urgent_alarm");
mSocket.off("clear_alarm");
rxBus.send(new EventNSClientNewLog("NSCLIENT", "destroy"));
isConnected = false;
hasWriteAuth = false;
mSocket.disconnect();
mSocket = null;
}
}
public void sendAuthMessage(NSAuthAck ack) {
JSONObject authMessage = new JSONObject();
try {
authMessage.put("client", "Android_" + nsDevice);
authMessage.put("history", nsHours);
authMessage.put("status", true); // receive status
authMessage.put("from", latestDateInReceivedData); // send data newer than
authMessage.put("secret", nsAPIhashCode);
} catch (JSONException e) {
aapsLogger.error("Unhandled exception", e);
return;
}
rxBus.send(new EventNSClientNewLog("AUTH", "requesting auth"));
if (mSocket != null)
mSocket.emit("authorize", authMessage, ack);
}
public void readPreferences() {
nsEnabled = nsClientPlugin.isEnabled(PluginType.GENERAL);
nsURL = sp.getString(R.string.key_nsclientinternal_url, "");
nsAPISecret = sp.getString(R.string.key_nsclientinternal_api_secret, "");
nsDevice = sp.getString("careportal_enteredby", "");
}
private final Emitter.Listener onError = new Emitter.Listener() {
@Override
public void call(final Object... args) {
String msg = "Unknown Error";
if (args.length > 0 && args[0] != null) {
msg = args[0].toString();
}
rxBus.send(new EventNSClientNewLog("ERROR", msg));
}
};
private final Emitter.Listener onPing = new Emitter.Listener() {
@Override
public void call(final Object... args) {
rxBus.send(new EventNSClientNewLog("PING", "received"));
// send data if there is something waiting
resend("Ping received");
}
};
private final Emitter.Listener onAnnouncement = new Emitter.Listener() {
/*
{
"level":0,
"title":"Announcement",
"message":"test",
"plugin":{"name":"treatmentnotify","label":"Treatment Notifications","pluginType":"notification","enabled":true},
"group":"Announcement",
"isAnnouncement":true,
"key":"9ac46ad9a1dcda79dd87dae418fce0e7955c68da"
}
*/
@Override
public void call(final Object... args) {
JSONObject data;
try {
data = (JSONObject) args[0];
handleAnnouncement(data);
} catch (Exception e) {
aapsLogger.error("Unhandled exception", e);
}
}
};
private final Emitter.Listener onAlarm = new Emitter.Listener() {
/*
{
"level":1,
"title":"Warning HIGH",
"message":"BG Now: 5 -0.2 → mmol\/L\nRaw BG: 4.8 mmol\/L Čistý\nBG 15m: 4.8 mmol\/L\nIOB: -0.02U\nCOB: 0g",
"eventName":"high",
"plugin":{"name":"simplealarms","label":"Simple Alarms","pluginType":"notification","enabled":true},
"pushoverSound":"climb",
"debug":{"lastSGV":5,"thresholds":{"bgHigh":180,"bgTargetTop":75,"bgTargetBottom":72,"bgLow":70}},
"group":"default",
"key":"simplealarms_1"
}
*/
@Override
public void call(final Object... args) {
JSONObject data;
try {
data = (JSONObject) args[0];
handleAlarm(data);
} catch (Exception e) {
aapsLogger.error("Unhandled exception", e);
}
}
};
private final Emitter.Listener onUrgentAlarm = args -> {
JSONObject data;
try {
data = (JSONObject) args[0];
handleUrgentAlarm(data);
} catch (Exception e) {
aapsLogger.error("Unhandled exception", e);
}
};
private final Emitter.Listener onClearAlarm = new Emitter.Listener() {
/*
{
"clear":true,
"title":"All Clear",
"message":"default - Urgent was ack'd",
"group":"default"
}
*/
@Override
public void call(final Object... args) {
JSONObject data;
try {
data = (JSONObject) args[0];
rxBus.send(new EventNSClientNewLog("CLEARALARM", "received"));
rxBus.send(new EventDismissNotification(Notification.NS_ALARM));
rxBus.send(new EventDismissNotification(Notification.NS_URGENT_ALARM));
aapsLogger.debug(LTag.NSCLIENT, data.toString());
} catch (Exception e) {
aapsLogger.error("Unhandled exception", e);
}
}
};
private final Emitter.Listener onDataUpdate = new Emitter.Listener() {
@Override
public void call(final Object... args) {
NSClientService.handler.post(() -> {
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"AndroidAPS:NSClientService_onDataUpdate");
wakeLock.acquire();
try {
JSONObject data = (JSONObject) args[0];
boolean broadcastProfile = false;
try {
// delta means only increment/changes are comming
boolean isDelta = data.has("delta");
boolean isFull = !isDelta;
rxBus.send(new EventNSClientNewLog("DATA", "Data packet #" + dataCounter++ + (isDelta ? " delta" : " full")));
if (data.has("status")) {
JSONObject status = data.getJSONObject("status");
nsSettingsStatus.handleNewData(status);
} else if (!isDelta) {
rxBus.send(new EventNSClientNewLog("ERROR", "Unsupported Nightscout version !!!!"));
}
if (data.has("profiles")) {
JSONArray profiles = data.getJSONArray("profiles");
if (profiles.length() > 0) {
// take the newest
JSONObject profileStoreJson = (JSONObject) profiles.get(profiles.length() - 1);
rxBus.send(new EventNSClientNewLog("PROFILE", "profile received"));
dataWorker.enqueue(
new OneTimeWorkRequest.Builder(NSProfilePlugin.NSProfileWorker.class)
.setInputData(dataWorker.storeInputData(profileStoreJson, null))
.build());
if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) {
Bundle bundle = new Bundle();
bundle.putString("profile", profileStoreJson.toString());
bundle.putBoolean("delta", isDelta);
Intent intent = new Intent(Intents.ACTION_NEW_PROFILE);
intent.putExtras(bundle);
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
sendBroadcast(intent);
}
}
}
if (data.has("treatments")) {
JSONArray treatments = data.getJSONArray("treatments");
JSONArray removedTreatments = new JSONArray();
JSONArray addedOrUpdatedTreatments = new JSONArray();
if (treatments.length() > 0)
rxBus.send(new EventNSClientNewLog("DATA", "received " + treatments.length() + " treatments"));
for (Integer index = 0; index < treatments.length(); index++) {
JSONObject jsonTreatment = treatments.getJSONObject(index);
String action = JsonHelper.safeGetStringAllowNull(jsonTreatment, "action", null);
long mills = JsonHelper.safeGetLong(jsonTreatment, "mills");
if (action == null) addedOrUpdatedTreatments.put(jsonTreatment);
else if (action.equals("update"))
addedOrUpdatedTreatments.put(jsonTreatment);
else if (action.equals("remove") && mills > dateUtil.now() - T.days(1).msecs()) // handle 1 day old deletions only
removedTreatments.put(jsonTreatment);
}
if (removedTreatments.length() > 0) {
dataWorker.enqueue(
new OneTimeWorkRequest.Builder(NSClientRemoveWorker.class)
.setInputData(dataWorker.storeInputData(removedTreatments, null))
.build());
if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) {
Bundle bundle = new Bundle();
bundle.putString("treatments", removedTreatments.toString());
bundle.putBoolean("delta", isDelta);
Intent intent = new Intent(Intents.ACTION_REMOVED_TREATMENT);
intent.putExtras(bundle);
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
sendBroadcast(intent);
}
}
if (addedOrUpdatedTreatments.length() > 0) {
dataWorker.enqueue(
new OneTimeWorkRequest.Builder(NSClientAddUpdateWorker.class)
.setInputData(dataWorker.storeInputData(addedOrUpdatedTreatments, null))
.build());
if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) {
List<JSONArray> splitted = splitArray(addedOrUpdatedTreatments);
for (JSONArray part : splitted) {
Bundle bundle = new Bundle();
bundle.putString("treatments", part.toString());
bundle.putBoolean("delta", isDelta);
Intent intent = new Intent(Intents.ACTION_CHANGED_TREATMENT);
intent.putExtras(bundle);
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
sendBroadcast(intent);
}
}
}
}
if (data.has("devicestatus")) {
JSONArray devicestatuses = data.getJSONArray("devicestatus");
if (devicestatuses.length() > 0) {
rxBus.send(new EventNSClientNewLog("DATA", "received " + devicestatuses.length() + " device statuses"));
nsDeviceStatus.handleNewData(devicestatuses);
}
}
if (data.has("food")) {
JSONArray foods = data.getJSONArray("food");
if (foods.length() > 0)
rxBus.send(new EventNSClientNewLog("DATA", "received " + foods.length() + " foods"));
dataWorker.enqueue(
new OneTimeWorkRequest.Builder(FoodPlugin.FoodWorker.class)
.setInputData(dataWorker.storeInputData(foods, null))
.build());
}
//noinspection SpellCheckingInspection
if (data.has("mbgs")) {
JSONArray mbgArray = data.getJSONArray("mbgs");
if (mbgArray.length() > 0)
rxBus.send(new EventNSClientNewLog("DATA", "received " + mbgArray.length() + " mbgs"));
dataWorker.enqueue(
new OneTimeWorkRequest.Builder(NSClientMbgWorker.class)
.setInputData(dataWorker.storeInputData(mbgArray, null))
.build());
}
if (data.has("cals")) {
JSONArray cals = data.getJSONArray("cals");
if (cals.length() > 0)
rxBus.send(new EventNSClientNewLog("DATA", "received " + cals.length() + " cals"));
// Calibrations ignored
}
if (data.has("sgvs")) {
JSONArray sgvs = data.getJSONArray("sgvs");
if (sgvs.length() > 0)
rxBus.send(new EventNSClientNewLog("DATA", "received " + sgvs.length() + " sgvs"));
dataWorker.enqueue(new OneTimeWorkRequest.Builder(NSClientSourcePlugin.NSClientSourceWorker.class)
.setInputData(dataWorker.storeInputData(sgvs, null))
.build());
List<JSONArray> splitted = splitArray(sgvs);
if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) {
for (JSONArray part : splitted) {
Bundle bundle = new Bundle();
bundle.putString("sgvs", part.toString());
bundle.putBoolean("delta", isDelta);
Intent intent = new Intent(Intents.ACTION_NEW_SGV);
intent.putExtras(bundle);
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
sendBroadcast(intent);
}
}
}
rxBus.send(new EventNSClientNewLog("LAST", dateUtil.dateAndTimeString(latestDateInReceivedData)));
} catch (JSONException e) {
aapsLogger.error("Unhandled exception", e);
}
//rxBus.send(new EventNSClientNewLog("NSCLIENT", "onDataUpdate end");
} finally {
if (wakeLock.isHeld()) wakeLock.release();
}
});
}
};
public void dbUpdate(DbRequest dbr, NSUpdateAck ack) {
try {
if (!isConnected || !hasWriteAuth) return;
JSONObject message = new JSONObject();
message.put("collection", dbr.collection);
message.put("_id", dbr._id);
message.put("data", new JSONObject(dbr.data));
mSocket.emit("dbUpdate", message, ack);
rxBus.send(new EventNSClientNewLog("DBUPDATE " + dbr.collection, "Sent " + dbr._id));
} catch (JSONException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public void dbUpdate(String collection, String _id, JSONObject data, Object originalObject) {
try {
if (!isConnected || !hasWriteAuth) return;
JSONObject message = new JSONObject();
message.put("collection", collection);
message.put("_id", _id);
message.put("data", data);
mSocket.emit("dbUpdate", message, new NSUpdateAck("dbUpdate", _id, aapsLogger, rxBus, originalObject));
rxBus.send(new EventNSClientNewLog("DBUPDATE " + collection, "Sent " + originalObject.getClass().getSimpleName() + " " + _id));
} catch (JSONException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public void dbRemove(DbRequest dbr, NSUpdateAck ack) {
try {
if (!isConnected || !hasWriteAuth) return;
JSONObject message = new JSONObject();
message.put("collection", dbr.collection);
message.put("_id", dbr._id);
mSocket.emit("dbRemove", message, ack);
rxBus.send(new EventNSClientNewLog("DBREMOVE " + dbr.collection, "Sent " + dbr._id));
} catch (JSONException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public void dbRemove(String collection, String _id, Object originalObject) {
try {
if (!isConnected || !hasWriteAuth) return;
JSONObject message = new JSONObject();
message.put("collection", collection);
message.put("_id", _id);
mSocket.emit("dbRemove", message, new NSUpdateAck("dbRemove", _id, aapsLogger, rxBus, originalObject));
rxBus.send(new EventNSClientNewLog("DBREMOVE " + collection, "Sent " + originalObject.getClass().getSimpleName() + " " + _id));
} catch (JSONException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public void dbAdd(DbRequest dbr, NSAddAck ack) {
try {
if (!isConnected || !hasWriteAuth) return;
JSONObject message = new JSONObject();
message.put("collection", dbr.collection);
message.put("data", new JSONObject(dbr.data));
mSocket.emit("dbAdd", message, ack);
rxBus.send(new EventNSClientNewLog("DBADD " + dbr.collection, "Sent " + dbr.nsClientID));
} catch (JSONException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public void dbAdd(String collection, JSONObject data, Object originalObject) {
try {
if (!isConnected || !hasWriteAuth) return;
JSONObject message = new JSONObject();
message.put("collection", collection);
message.put("data", data);
mSocket.emit("dbAdd", message, new NSAddAck(aapsLogger, rxBus, originalObject));
rxBus.send(new EventNSClientNewLog("DBADD " + collection, "Sent " + originalObject.getClass().getSimpleName() + " " + data));
} catch (JSONException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public void sendAlarmAck(AlarmAck alarmAck) {
if (!isConnected || !hasWriteAuth) return;
mSocket.emit("ack", alarmAck.level, alarmAck.group, alarmAck.silenceTime);
rxBus.send(new EventNSClientNewLog("ALARMACK ", alarmAck.level + " " + alarmAck.group + " " + alarmAck.silenceTime));
}
public void resend(final String reason) {
if (!isConnected || !hasWriteAuth) return;
handler.post(() -> {
if (mSocket == null || !mSocket.connected()) return;
rxBus.send(new EventNSClientNewLog("QUEUE", "Resend started: " + reason));
if (lastAckTime > System.currentTimeMillis() - 10 * 1000L) {
aapsLogger.debug(LTag.NSCLIENT, "Skipping resend by lastAckTime: " + ((System.currentTimeMillis() - lastAckTime) / 1000L) + " sec");
return;
}
dataSyncSelector.processChangedBolusesCompat();
dataSyncSelector.processChangedCarbsCompat();
dataSyncSelector.processChangedBolusCalculatorResultsCompat();
dataSyncSelector.processChangedTemporaryBasalsCompat();
dataSyncSelector.processChangedExtendedBolusesCompat();
dataSyncSelector.processChangedProfileSwitchesCompat();
dataSyncSelector.processChangedGlucoseValuesCompat();
dataSyncSelector.processChangedTempTargetsCompat();
dataSyncSelector.processChangedFoodsCompat();
dataSyncSelector.processChangedTherapyEventsCompat();
dataSyncSelector.processChangedDeviceStatusesCompat();
if (uploadQueue.size() == 0)
return;
if (lastResendTime > System.currentTimeMillis() - 10 * 1000L) {
aapsLogger.debug(LTag.NSCLIENT, "Skipping resend by lastResendTime: " + ((System.currentTimeMillis() - lastResendTime) / 1000L) + " sec");
return;
}
lastResendTime = System.currentTimeMillis();
CloseableIterator<DbRequest> iterator;
int maxcount = 30;
try {
iterator = databaseHelper.getDbRequestIterator();
try {
while (iterator.hasNext() && maxcount > 0) {
DbRequest dbr = iterator.next();
if (dbr.action.equals("dbAdd")) {
NSAddAck addAck = new NSAddAck(aapsLogger, rxBus, null);
dbAdd(dbr, addAck);
} else if (dbr.action.equals("dbRemove")) {
NSUpdateAck removeAck = new NSUpdateAck("dbRemove", dbr._id, aapsLogger, rxBus, null);
dbRemove(dbr, removeAck);
} else if (dbr.action.equals("dbUpdate")) {
NSUpdateAck updateAck = new NSUpdateAck("dbUpdate", dbr._id, aapsLogger, rxBus, null);
dbUpdate(dbr, updateAck);
}
maxcount--;
}
} finally {
iterator.close();
}
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
rxBus.send(new EventNSClientNewLog("QUEUE", "Resend ended: " + reason));
});
}
public void restart() {
destroy();
initialize();
}
private void handleAnnouncement(JSONObject announcement) {
boolean defaultVal = config.getNSCLIENT();
if (sp.getBoolean(R.string.key_ns_announcements, defaultVal)) {
NSAlarm nsAlarm = new NSAlarm(announcement);
Notification notification = new NotificationWithAction(injector, nsAlarm);
rxBus.send(new EventNewNotification(notification));
rxBus.send(new EventNSClientNewLog("ANNOUNCEMENT", JsonHelper.safeGetString(announcement, "message", "received")));
aapsLogger.debug(LTag.NSCLIENT, announcement.toString());
}
}
private void handleAlarm(JSONObject alarm) {
boolean defaultVal = config.getNSCLIENT();
if (sp.getBoolean(R.string.key_ns_alarms, defaultVal)) {
long snoozedTo = sp.getLong(R.string.key_snoozedTo, 0L);
if (snoozedTo == 0L || System.currentTimeMillis() > snoozedTo) {
NSAlarm nsAlarm = new NSAlarm(alarm);
Notification notification = new NotificationWithAction(injector, nsAlarm);
rxBus.send(new EventNewNotification(notification));
}
rxBus.send(new EventNSClientNewLog("ALARM", JsonHelper.safeGetString(alarm, "message", "received")));
aapsLogger.debug(LTag.NSCLIENT, alarm.toString());
}
}
private void handleUrgentAlarm(JSONObject alarm) {
boolean defaultVal = config.getNSCLIENT();
if (sp.getBoolean(R.string.key_ns_alarms, defaultVal)) {
long snoozedTo = sp.getLong(R.string.key_snoozedTo, 0L);
if (snoozedTo == 0L || System.currentTimeMillis() > snoozedTo) {
NSAlarm nsAlarm = new NSAlarm(alarm);
Notification notification = new NotificationWithAction(injector, nsAlarm);
rxBus.send(new EventNewNotification(notification));
}
rxBus.send(new EventNSClientNewLog("URGENTALARM", JsonHelper.safeGetString(alarm, "message", "received")));
aapsLogger.debug(LTag.NSCLIENT, alarm.toString());
}
}
public List<JSONArray> splitArray(JSONArray array) {
List<JSONArray> ret = new ArrayList<>();
try {
int size = array.length();
int count = 0;
JSONArray newarr = null;
for (int i = 0; i < size; i++) {
if (count == 0) {
if (newarr != null) {
ret.add(newarr);
}
newarr = new JSONArray();
count = 20;
}
newarr.put(array.get(i));
--count;
}
if (newarr != null && newarr.length() > 0) {
ret.add(newarr);
}
} catch (JSONException e) {
aapsLogger.error("Unhandled exception", e);
ret = new ArrayList<>();
ret.add(array);
}
return ret;
}
}

View file

@ -0,0 +1,731 @@
package info.nightscout.androidaps.plugins.general.nsclient.services
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.*
import androidx.work.OneTimeWorkRequest
import com.google.common.base.Charsets
import com.google.common.hash.Hashing
import dagger.android.DaggerService
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.events.EventAppExit
import info.nightscout.androidaps.events.EventConfigBuilderChange
import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.DataSyncSelector
import info.nightscout.androidaps.interfaces.DatabaseHelperInterface
import info.nightscout.androidaps.interfaces.PluginType
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.food.FoodPlugin.FoodWorker
import info.nightscout.androidaps.plugins.general.nsclient.*
import info.nightscout.androidaps.plugins.general.nsclient.acks.NSAddAck
import info.nightscout.androidaps.plugins.general.nsclient.acks.NSAuthAck
import info.nightscout.androidaps.plugins.general.nsclient.acks.NSUpdateAck
import info.nightscout.androidaps.plugins.general.nsclient.data.AlarmAck
import info.nightscout.androidaps.plugins.general.nsclient.data.NSAlarm
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientNewLog
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientUpdateGUI
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.notifications.Notification
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationWithAction
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.source.NSClientSourcePlugin.NSClientSourceWorker
import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.services.Intents
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.JsonHelper.safeGetLong
import info.nightscout.androidaps.utils.JsonHelper.safeGetString
import info.nightscout.androidaps.utils.JsonHelper.safeGetStringAllowNull
import info.nightscout.androidaps.utils.T.Companion.days
import info.nightscout.androidaps.utils.T.Companion.mins
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
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.socket.client.IO
import io.socket.client.Socket
import io.socket.emitter.Emitter
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.net.URISyntaxException
import java.util.*
import javax.inject.Inject
class NSClientService : DaggerService() {
@Inject lateinit var injector: HasAndroidInjector
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var nsSettingsStatus: NSSettingsStatus
@Inject lateinit var nsDeviceStatus: NSDeviceStatus
@Inject lateinit var databaseHelper: DatabaseHelperInterface
@Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var sp: SP
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var nsClientPlugin: NSClientPlugin
@Inject lateinit var buildHelper: BuildHelper
@Inject lateinit var config: Config
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var dataWorker: DataWorker
@Inject lateinit var dataSyncSelector: DataSyncSelector
@Inject lateinit var repository: AppRepository
companion object {
private const val WATCHDOG_INTERVAL_MINUTES = 2
private const val WATCHDOG_RECONNECT_IN = 15
private const val WATCHDOG_MAX_CONNECTIONS = 5
}
private val disposable = CompositeDisposable()
private var wakeLock: PowerManager.WakeLock? = null
private val binder: IBinder = LocalBinder()
private var handler: Handler? = null
private var socket: Socket? = null
private var dataCounter = 0
private var connectCounter = 0
private var nsEnabled = false
private var nsAPISecret = ""
private var nsDevice = ""
private val nsHours = 48
private var lastAckTime: Long = 0
private var nsApiHashCode = ""
private val reconnections = ArrayList<Long>()
var isConnected = false
var hasWriteAuth = false
var nsURL = ""
var latestDateInReceivedData: Long = 0
@SuppressLint("WakelockTimeout")
@kotlin.ExperimentalStdlibApi
override fun onCreate() {
super.onCreate()
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AndroidAPS:NSClientService")
wakeLock?.acquire()
initialize()
disposable.add(rxBus
.toObservable(EventConfigBuilderChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
if (nsEnabled != nsClientPlugin.isEnabled(PluginType.GENERAL)) {
latestDateInReceivedData = 0
destroy()
initialize()
}
}, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventPreferenceChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ event: EventPreferenceChange ->
if (event.isChanged(resourceHelper, R.string.key_nsclientinternal_url) ||
event.isChanged(resourceHelper, R.string.key_nsclientinternal_api_secret) ||
event.isChanged(resourceHelper, R.string.key_nsclientinternal_paused)) {
latestDateInReceivedData = 0
destroy()
initialize()
}
}, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventAppExit::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
aapsLogger.debug(LTag.NSCLIENT, "EventAppExit received")
destroy()
stopSelf()
}, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventNSClientRestart::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
latestDateInReceivedData = 0
restart()
}, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(NSAuthAck::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ ack -> processAuthAck(ack) }, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(NSUpdateAck::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ ack -> processUpdateAck(ack) }, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(NSAddAck::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ ack -> processAddAck(ack) }, fabricPrivacy::logException)
)
}
override fun onDestroy() {
super.onDestroy()
disposable.clear()
if (wakeLock?.isHeld == true) wakeLock?.release()
}
private fun processAddAck(ack: NSAddAck) {
lastAckTime = dateUtil.now()
dataWorker.enqueue(
OneTimeWorkRequest.Builder(NSClientAddAckWorker::class.java)
.setInputData(dataWorker.storeInputData(ack, null))
.build())
}
private fun processUpdateAck(ack: NSUpdateAck) {
lastAckTime = dateUtil.now()
dataWorker.enqueue(
OneTimeWorkRequest.Builder(NSClientUpdateRemoveAckWorker::class.java)
.setInputData(dataWorker.storeInputData(ack, null))
.build())
}
fun processAuthAck(ack: NSAuthAck) {
var connectionStatus = "Authenticated ("
if (ack.read) connectionStatus += "R"
if (ack.write) connectionStatus += "W"
if (ack.write_treatment) connectionStatus += "T"
connectionStatus += ')'
isConnected = true
hasWriteAuth = ack.write && ack.write_treatment
rxBus.send(EventNSClientStatus(connectionStatus))
rxBus.send(EventNSClientNewLog("AUTH", connectionStatus))
if (!ack.write) {
rxBus.send(EventNSClientNewLog("ERROR", "Write permission not granted "))
}
if (!ack.write_treatment) {
rxBus.send(EventNSClientNewLog("ERROR", "Write treatment permission not granted "))
}
if (!hasWriteAuth) {
val noWritePerm = Notification(Notification.NSCLIENT_NO_WRITE_PERMISSION, resourceHelper.gs(R.string.nowritepermission), Notification.URGENT)
rxBus.send(EventNewNotification(noWritePerm))
} else {
rxBus.send(EventDismissNotification(Notification.NSCLIENT_NO_WRITE_PERMISSION))
}
}
inner class LocalBinder : Binder() {
val serviceInstance: NSClientService
get() = this@NSClientService
}
override fun onBind(intent: Intent): IBinder {
return binder
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
return START_STICKY
}
@kotlin.ExperimentalStdlibApi
fun initialize() {
dataCounter = 0
readPreferences()
@Suppress("UnstableApiUsage", "DEPRECATION")
if (nsAPISecret != "") nsApiHashCode = Hashing.sha1().hashString(nsAPISecret, Charsets.UTF_8).toString()
rxBus.send(EventNSClientStatus("Initializing"))
if (!nsClientPlugin.isAllowed) {
rxBus.send(EventNSClientNewLog("NSCLIENT", "not allowed"))
rxBus.send(EventNSClientStatus("Not allowed"))
} else if (nsClientPlugin.paused) {
rxBus.send(EventNSClientNewLog("NSCLIENT", "paused"))
rxBus.send(EventNSClientStatus("Paused"))
} else if (!nsEnabled) {
rxBus.send(EventNSClientNewLog("NSCLIENT", "disabled"))
rxBus.send(EventNSClientStatus("Disabled"))
} else if (nsURL != "" && (buildHelper.isEngineeringMode() || nsURL.lowercase(Locale.getDefault()).startsWith("https://"))) {
try {
rxBus.send(EventNSClientStatus("Connecting ..."))
val opt = IO.Options()
opt.forceNew = true
opt.reconnection = true
socket = IO.socket(nsURL, opt).also { socket ->
socket.on(Socket.EVENT_CONNECT, onConnect)
socket.on(Socket.EVENT_DISCONNECT, onDisconnect)
socket.on(Socket.EVENT_ERROR, onError)
socket.on(Socket.EVENT_CONNECT_ERROR, onError)
socket.on(Socket.EVENT_CONNECT_TIMEOUT, onError)
socket.on(Socket.EVENT_PING, onPing)
rxBus.send(EventNSClientNewLog("NSCLIENT", "do connect"))
socket.connect()
socket.on("dataUpdate", onDataUpdate)
socket.on("announcement", onAnnouncement)
socket.on("alarm", onAlarm)
socket.on("urgent_alarm", onUrgentAlarm)
socket.on("clear_alarm", onClearAlarm)
}
} catch (e: URISyntaxException) {
rxBus.send(EventNSClientNewLog("NSCLIENT", "Wrong URL syntax"))
rxBus.send(EventNSClientStatus("Wrong URL syntax"))
} catch (e: RuntimeException) {
rxBus.send(EventNSClientNewLog("NSCLIENT", "Wrong URL syntax"))
rxBus.send(EventNSClientStatus("Wrong URL syntax"))
}
} else if (nsURL.lowercase(Locale.getDefault()).startsWith("http://")) {
rxBus.send(EventNSClientNewLog("NSCLIENT", "NS URL not encrypted"))
rxBus.send(EventNSClientStatus("Not encrypted"))
} else {
rxBus.send(EventNSClientNewLog("NSCLIENT", "No NS URL specified"))
rxBus.send(EventNSClientStatus("Not configured"))
}
}
private val onConnect = Emitter.Listener {
connectCounter++
val socketId = socket?.id() ?: "NULL"
rxBus.send(EventNSClientNewLog("NSCLIENT", "connect #$connectCounter event. ID: $socketId"))
if (socket != null) sendAuthMessage(NSAuthAck(rxBus))
watchdog()
}
private fun watchdog() {
synchronized(reconnections) {
val now = dateUtil.now()
reconnections.add(now)
for (r in reconnections.reversed()) {
if (r < now - mins(WATCHDOG_INTERVAL_MINUTES.toLong()).msecs()) {
reconnections.remove(r)
}
}
rxBus.send(EventNSClientNewLog("WATCHDOG", "connections in last " + WATCHDOG_INTERVAL_MINUTES + " minutes: " + reconnections.size + "/" + WATCHDOG_MAX_CONNECTIONS))
if (reconnections.size >= WATCHDOG_MAX_CONNECTIONS) {
val n = Notification(Notification.NS_MALFUNCTION, resourceHelper.gs(R.string.nsmalfunction), Notification.URGENT)
rxBus.send(EventNewNotification(n))
rxBus.send(EventNSClientNewLog("WATCHDOG", "pausing for $WATCHDOG_RECONNECT_IN minutes"))
nsClientPlugin.pause(true)
rxBus.send(EventNSClientUpdateGUI())
Thread {
SystemClock.sleep(mins(WATCHDOG_RECONNECT_IN.toLong()).msecs())
rxBus.send(EventNSClientNewLog("WATCHDOG", "re-enabling NSClient"))
nsClientPlugin.pause(false)
}.start()
}
}
}
private val onDisconnect = Emitter.Listener { args ->
aapsLogger.debug(LTag.NSCLIENT, "disconnect reason: {}", *args)
rxBus.send(EventNSClientNewLog("NSCLIENT", "disconnect event"))
}
@Synchronized fun destroy() {
socket?.off(Socket.EVENT_CONNECT)
socket?.off(Socket.EVENT_DISCONNECT)
socket?.off(Socket.EVENT_PING)
socket?.off("dataUpdate")
socket?.off("announcement")
socket?.off("alarm")
socket?.off("urgent_alarm")
socket?.off("clear_alarm")
rxBus.send(EventNSClientNewLog("NSCLIENT", "destroy"))
isConnected = false
hasWriteAuth = false
socket?.disconnect()
socket = null
}
private fun sendAuthMessage(ack: NSAuthAck?) {
val authMessage = JSONObject()
try {
authMessage.put("client", "Android_$nsDevice")
authMessage.put("history", nsHours)
authMessage.put("status", true) // receive status
authMessage.put("from", latestDateInReceivedData) // send data newer than
authMessage.put("secret", nsApiHashCode)
} catch (e: JSONException) {
aapsLogger.error("Unhandled exception", e)
return
}
rxBus.send(EventNSClientNewLog("AUTH", "requesting auth"))
socket?.emit("authorize", authMessage, ack)
}
fun readPreferences() {
nsEnabled = nsClientPlugin.isEnabled(PluginType.GENERAL)
nsURL = sp.getString(R.string.key_nsclientinternal_url, "")
nsAPISecret = sp.getString(R.string.key_nsclientinternal_api_secret, "")
nsDevice = sp.getString("careportal_enteredby", "")
}
private val onError = Emitter.Listener { args ->
var msg = "Unknown Error"
if (args.isNotEmpty() && args[0] != null) {
msg = args[0].toString()
}
rxBus.send(EventNSClientNewLog("ERROR", msg))
}
private val onPing = Emitter.Listener {
rxBus.send(EventNSClientNewLog("PING", "received"))
// send data if there is something waiting
resend("Ping received")
}
private val onAnnouncement = Emitter.Listener { args ->
/*
{
"level":0,
"title":"Announcement",
"message":"test",
"plugin":{"name":"treatmentnotify","label":"Treatment Notifications","pluginType":"notification","enabled":true},
"group":"Announcement",
"isAnnouncement":true,
"key":"9ac46ad9a1dcda79dd87dae418fce0e7955c68da"
}
*/
val data: JSONObject
try {
data = args[0] as JSONObject
handleAnnouncement(data)
} catch (e: Exception) {
aapsLogger.error("Unhandled exception", e)
}
}
private val onAlarm = Emitter.Listener { args ->
/*
{
"level":1,
"title":"Warning HIGH",
"message":"BG Now: 5 -0.2 → mmol\/L\nRaw BG: 4.8 mmol\/L Čistý\nBG 15m: 4.8 mmol\/L\nIOB: -0.02U\nCOB: 0g",
"eventName":"high",
"plugin":{"name":"simplealarms","label":"Simple Alarms","pluginType":"notification","enabled":true},
"pushoverSound":"climb",
"debug":{"lastSGV":5,"thresholds":{"bgHigh":180,"bgTargetTop":75,"bgTargetBottom":72,"bgLow":70}},
"group":"default",
"key":"simplealarms_1"
}
*/
val data: JSONObject
try {
data = args[0] as JSONObject
handleAlarm(data)
} catch (e: Exception) {
aapsLogger.error("Unhandled exception", e)
}
}
private val onUrgentAlarm = Emitter.Listener { args: Array<Any> ->
val data: JSONObject
try {
data = args[0] as JSONObject
handleUrgentAlarm(data)
} catch (e: Exception) {
aapsLogger.error("Unhandled exception", e)
}
}
private val onClearAlarm = Emitter.Listener { args ->
/*
{
"clear":true,
"title":"All Clear",
"message":"default - Urgent was ack'd",
"group":"default"
}
*/
val data: JSONObject
try {
data = args[0] as JSONObject
rxBus.send(EventNSClientNewLog("CLEARALARM", "received"))
rxBus.send(EventDismissNotification(Notification.NS_ALARM))
rxBus.send(EventDismissNotification(Notification.NS_URGENT_ALARM))
aapsLogger.debug(LTag.NSCLIENT, data.toString())
} catch (e: Exception) {
aapsLogger.error("Unhandled exception", e)
}
}
private val onDataUpdate = Emitter.Listener { args ->
handler?.post {
// val powerManager = getSystemService(POWER_SERVICE) as PowerManager
// val wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
// "AndroidAPS:NSClientService_onDataUpdate")
// wakeLock.acquire(3000)
try {
val data = args[0] as JSONObject
try {
// delta means only increment/changes are coming
val isDelta = data.has("delta")
rxBus.send(EventNSClientNewLog("DATA", "Data packet #" + dataCounter++ + if (isDelta) " delta" else " full"))
if (data.has("status")) {
val status = data.getJSONObject("status")
nsSettingsStatus.handleNewData(status)
} else if (!isDelta) {
rxBus.send(EventNSClientNewLog("ERROR", "Unsupported Nightscout version "))
}
if (data.has("profiles")) {
val profiles = data.getJSONArray("profiles")
if (profiles.length() > 0) {
// take the newest
val profileStoreJson = profiles[profiles.length() - 1] as JSONObject
rxBus.send(EventNSClientNewLog("PROFILE", "profile received"))
dataWorker.enqueue(
OneTimeWorkRequest.Builder(LocalProfilePlugin.NSProfileWorker::class.java)
.setInputData(dataWorker.storeInputData(profileStoreJson, null))
.build())
if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) {
val bundle = Bundle()
bundle.putString("profile", profileStoreJson.toString())
bundle.putBoolean("delta", isDelta)
val intent = Intent(Intents.ACTION_NEW_PROFILE)
intent.putExtras(bundle)
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
sendBroadcast(intent)
}
}
}
if (data.has("treatments")) {
val treatments = data.getJSONArray("treatments")
val removedTreatments = JSONArray()
val addedOrUpdatedTreatments = JSONArray()
if (treatments.length() > 0) rxBus.send(EventNSClientNewLog("DATA", "received " + treatments.length() + " treatments"))
for (index in 0 until treatments.length()) {
val jsonTreatment = treatments.getJSONObject(index)
val action = safeGetStringAllowNull(jsonTreatment, "action", null)
val mills = safeGetLong(jsonTreatment, "mills")
if (action == null) addedOrUpdatedTreatments.put(jsonTreatment) else if (action == "update") addedOrUpdatedTreatments.put(jsonTreatment) else if (action == "remove" && mills > dateUtil.now() - days(1).msecs()) // handle 1 day old deletions only
removedTreatments.put(jsonTreatment)
}
if (removedTreatments.length() > 0) {
dataWorker.enqueue(
OneTimeWorkRequest.Builder(NSClientRemoveWorker::class.java)
.setInputData(dataWorker.storeInputData(removedTreatments, null))
.build())
if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) {
val bundle = Bundle()
bundle.putString("treatments", removedTreatments.toString())
bundle.putBoolean("delta", isDelta)
val intent = Intent(Intents.ACTION_REMOVED_TREATMENT)
intent.putExtras(bundle)
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
sendBroadcast(intent)
}
}
if (addedOrUpdatedTreatments.length() > 0) {
dataWorker.enqueue(
OneTimeWorkRequest.Builder(NSClientAddUpdateWorker::class.java)
.setInputData(dataWorker.storeInputData(addedOrUpdatedTreatments, null))
.build())
if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) {
val splitted = splitArray(addedOrUpdatedTreatments)
for (part in splitted) {
val bundle = Bundle()
bundle.putString("treatments", part.toString())
bundle.putBoolean("delta", isDelta)
val intent = Intent(Intents.ACTION_CHANGED_TREATMENT)
intent.putExtras(bundle)
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
sendBroadcast(intent)
}
}
}
}
if (data.has("devicestatus")) {
val devicestatuses = data.getJSONArray("devicestatus")
if (devicestatuses.length() > 0) {
rxBus.send(EventNSClientNewLog("DATA", "received " + devicestatuses.length() + " device statuses"))
nsDeviceStatus.handleNewData(devicestatuses)
}
}
if (data.has("food")) {
val foods = data.getJSONArray("food")
if (foods.length() > 0) rxBus.send(EventNSClientNewLog("DATA", "received " + foods.length() + " foods"))
dataWorker.enqueue(
OneTimeWorkRequest.Builder(FoodWorker::class.java)
.setInputData(dataWorker.storeInputData(foods, null))
.build())
}
if (data.has("mbgs")) {
val mbgArray = data.getJSONArray("mbgs")
if (mbgArray.length() > 0) rxBus.send(EventNSClientNewLog("DATA", "received " + mbgArray.length() + " mbgs"))
dataWorker.enqueue(
OneTimeWorkRequest.Builder(NSClientMbgWorker::class.java)
.setInputData(dataWorker.storeInputData(mbgArray, null))
.build())
}
if (data.has("cals")) {
val cals = data.getJSONArray("cals")
if (cals.length() > 0) rxBus.send(EventNSClientNewLog("DATA", "received " + cals.length() + " cals"))
// Calibrations ignored
}
if (data.has("sgvs")) {
val sgvs = data.getJSONArray("sgvs")
if (sgvs.length() > 0) rxBus.send(EventNSClientNewLog("DATA", "received " + sgvs.length() + " sgvs"))
dataWorker.enqueue(OneTimeWorkRequest.Builder(NSClientSourceWorker::class.java)
.setInputData(dataWorker.storeInputData(sgvs, null))
.build())
val splitted = splitArray(sgvs)
if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) {
for (part in splitted) {
val bundle = Bundle()
bundle.putString("sgvs", part.toString())
bundle.putBoolean("delta", isDelta)
val intent = Intent(Intents.ACTION_NEW_SGV)
intent.putExtras(bundle)
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
sendBroadcast(intent)
}
}
}
rxBus.send(EventNSClientNewLog("LAST", dateUtil.dateAndTimeString(latestDateInReceivedData)))
} catch (e: JSONException) {
aapsLogger.error("Unhandled exception", e)
}
//rxBus.send(new EventNSClientNewLog("NSCLIENT", "onDataUpdate end");
} finally {
// if (wakeLock.isHeld) wakeLock.release()
}
}
}
fun dbUpdate(collection: String, _id: String?, data: JSONObject?, originalObject: Any, progress: String) {
try {
if (_id == null) return
if (!isConnected || !hasWriteAuth) return
val message = JSONObject()
message.put("collection", collection)
message.put("_id", _id)
message.put("data", data)
socket?.emit("dbUpdate", message, NSUpdateAck("dbUpdate", _id, aapsLogger, rxBus, originalObject))
rxBus.send(EventNSClientNewLog("DBUPDATE $collection", "Sent " + originalObject.javaClass.simpleName + " " + _id + " " + progress))
} catch (e: JSONException) {
aapsLogger.error("Unhandled exception", e)
}
}
fun dbAdd(collection: String, data: JSONObject, originalObject: Any, progress: String) {
try {
if (!isConnected || !hasWriteAuth) return
val message = JSONObject()
message.put("collection", collection)
message.put("data", data)
socket?.emit("dbAdd", message, NSAddAck(aapsLogger, rxBus, originalObject))
rxBus.send(EventNSClientNewLog("DBADD $collection", "Sent " + originalObject.javaClass.simpleName + " " + data + " " + progress))
} catch (e: JSONException) {
aapsLogger.error("Unhandled exception", e)
}
}
fun sendAlarmAck(alarmAck: AlarmAck) {
if (!isConnected || !hasWriteAuth) return
socket?.emit("ack", alarmAck.level, alarmAck.group, alarmAck.silenceTime)
rxBus.send(EventNSClientNewLog("ALARMACK ", alarmAck.level.toString() + " " + alarmAck.group + " " + alarmAck.silenceTime))
}
fun resend(reason: String) {
if (!isConnected || !hasWriteAuth) return
handler?.post {
if (socket?.connected() != true) return@post
if (lastAckTime > System.currentTimeMillis() - 10 * 1000L) {
aapsLogger.debug(LTag.NSCLIENT, "Skipping resend by lastAckTime: " + (System.currentTimeMillis() - lastAckTime) / 1000L + " sec")
return@post
}
// val powerManager = getSystemService(POWER_SERVICE) as PowerManager
// val wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
// "AndroidAPS:NSClientService_onDataUpdate")
// wakeLock.acquire(mins(10).msecs())
try {
rxBus.send(EventNSClientNewLog("QUEUE", "Resend started: $reason"))
dataSyncSelector.doUpload()
rxBus.send(EventNSClientNewLog("QUEUE", "Resend ended: $reason"))
} finally {
// if (wakeLock.isHeld) wakeLock.release()
}
}
}
@kotlin.ExperimentalStdlibApi
fun restart() {
destroy()
initialize()
}
private fun handleAnnouncement(announcement: JSONObject) {
val defaultVal = config.NSCLIENT
if (sp.getBoolean(R.string.key_ns_announcements, defaultVal)) {
val nsAlarm = NSAlarm(announcement)
val notification: Notification = NotificationWithAction(injector, nsAlarm)
rxBus.send(EventNewNotification(notification))
rxBus.send(EventNSClientNewLog("ANNOUNCEMENT", safeGetString(announcement, "message", "received")))
aapsLogger.debug(LTag.NSCLIENT, announcement.toString())
}
}
private fun handleAlarm(alarm: JSONObject) {
val defaultVal = config.NSCLIENT
if (sp.getBoolean(R.string.key_ns_alarms, defaultVal)) {
val snoozedTo = sp.getLong(R.string.key_snoozedTo, 0L)
if (snoozedTo == 0L || System.currentTimeMillis() > snoozedTo) {
val nsAlarm = NSAlarm(alarm)
val notification: Notification = NotificationWithAction(injector, nsAlarm)
rxBus.send(EventNewNotification(notification))
}
rxBus.send(EventNSClientNewLog("ALARM", safeGetString(alarm, "message", "received")))
aapsLogger.debug(LTag.NSCLIENT, alarm.toString())
}
}
private fun handleUrgentAlarm(alarm: JSONObject) {
val defaultVal = config.NSCLIENT
if (sp.getBoolean(R.string.key_ns_alarms, defaultVal)) {
val snoozedTo = sp.getLong(R.string.key_snoozedTo, 0L)
if (snoozedTo == 0L || System.currentTimeMillis() > snoozedTo) {
val nsAlarm = NSAlarm(alarm)
val notification: Notification = NotificationWithAction(injector, nsAlarm)
rxBus.send(EventNewNotification(notification))
}
rxBus.send(EventNSClientNewLog("URGENTALARM", safeGetString(alarm, "message", "received")))
aapsLogger.debug(LTag.NSCLIENT, alarm.toString())
}
}
private fun splitArray(array: JSONArray): List<JSONArray> {
var ret: MutableList<JSONArray> = ArrayList()
try {
val size = array.length()
var count = 0
var newarr: JSONArray? = null
for (i in 0 until size) {
if (count == 0) {
if (newarr != null) ret.add(newarr)
newarr = JSONArray()
count = 20
}
newarr?.put(array[i])
--count
}
if (newarr != null && newarr.length() > 0) ret.add(newarr)
} catch (e: JSONException) {
aapsLogger.error("Unhandled exception", e)
ret = ArrayList()
ret.add(array)
}
return ret
}
init {
if (handler == null) {
val handlerThread = HandlerThread(NSClientService::class.java.simpleName + "Handler")
handlerThread.start()
handler = Handler(handlerThread.looper)
}
}
}

View file

@ -2,8 +2,10 @@ package info.nightscout.androidaps.plugins.general.openhumans
import java.util.* import java.util.*
fun String.isAllowedKey() = if (startsWith("ConfigBuilder_")) true else allowedKeys.contains(this.toUpperCase(Locale.ROOT)) @kotlin.ExperimentalStdlibApi
fun String.isAllowedKey() = if (startsWith("ConfigBuilder_")) true else allowedKeys.contains(this.uppercase(Locale.ROOT))
@kotlin.ExperimentalStdlibApi
private val allowedKeys = """ private val allowedKeys = """
absorption absorption
absorption_maxtime absorption_maxtime
@ -206,4 +208,4 @@ private val allowedKeys = """
xdripstatus xdripstatus
xdripstatus_detailediob xdripstatus_detailediob
xdripstatus_showbgi xdripstatus_showbgi
""".trimIndent().split("\n").filterNot { it.isBlank() }.map { it.toUpperCase() } """.trimIndent().split("\n").filterNot { it.isBlank() }.map { it.uppercase(Locale.getDefault()) }

View file

@ -28,6 +28,7 @@ class OHUploadWorker(context: Context, workerParameters: WorkerParameters)
@Inject @Inject
lateinit var resourceHelper: ResourceHelper lateinit var resourceHelper: ResourceHelper
@kotlin.ExperimentalStdlibApi
override fun createWork(): Single<Result> = Single.defer { override fun createWork(): Single<Result> = Single.defer {
// Here we inject every time we create work // Here we inject every time we create work

View file

@ -178,24 +178,24 @@ class OpenHumansUploader @Inject constructor(
} }
} }
@JvmOverloads // @JvmOverloads
fun enqueueTreatment(treatment: Treatment?, deleted: Boolean = false) = treatment?.let { // fun enqueueTreatment(treatment: Treatment?, deleted: Boolean = false) = treatment?.let {
insertQueueItem("Treatments") { // insertQueueItem("Treatments") {
put("date", treatment.date) // put("date", treatment.date)
put("isValid", treatment.isValid) // put("isValid", treatment.isValid)
put("source", treatment.source) // put("source", treatment.source)
put("nsId", treatment._id) // put("nsId", treatment._id)
put("boluscalc", treatment.boluscalc) // put("boluscalc", treatment.boluscalc)
put("carbs", treatment.carbs) // put("carbs", treatment.carbs)
put("dia", treatment.dia) // put("dia", treatment.dia)
put("insulin", treatment.insulin) // put("insulin", treatment.insulin)
put("insulinInterfaceID", treatment.insulinInterfaceID) // put("insulinInterfaceID", treatment.insulinInterfaceID)
put("isSMB", treatment.isSMB) // put("isSMB", treatment.isSMB)
put("mealBolus", treatment.mealBolus) // put("mealBolus", treatment.mealBolus)
put("bolusCalcJson", treatment.getBoluscalc()) // put("bolusCalcJson", treatment.getBoluscalc())
put("isDeletion", deleted) // put("isDeletion", deleted)
} // }
} // }
@JvmOverloads @JvmOverloads
fun enqueueTherapyEvent(therapyEvent: TherapyEvent, deleted: Boolean = false) = insertQueueItem("TherapyEvents") { fun enqueueTherapyEvent(therapyEvent: TherapyEvent, deleted: Boolean = false) = insertQueueItem("TherapyEvents") {
@ -210,17 +210,17 @@ class OpenHumansUploader @Inject constructor(
put("isDeletion", deleted) put("isDeletion", deleted)
} }
@JvmOverloads // @JvmOverloads
fun enqueueExtendedBolus(extendedBolus: ExtendedBolus, deleted: Boolean = false) = insertQueueItem("ExtendedBoluses") { // fun enqueueExtendedBolus(extendedBolus: ExtendedBolus, deleted: Boolean = false) = insertQueueItem("ExtendedBoluses") {
put("date", extendedBolus.date) // put("date", extendedBolus.date)
put("isValid", extendedBolus.isValid) // put("isValid", extendedBolus.isValid)
put("source", extendedBolus.source) // put("source", extendedBolus.source)
put("nsId", extendedBolus._id) // put("nsId", extendedBolus._id)
put("pumpId", extendedBolus.pumpId) // put("pumpId", extendedBolus.pumpId)
put("insulin", extendedBolus.insulin) // put("insulin", extendedBolus.insulin)
put("durationInMinutes", extendedBolus.durationInMinutes) // put("durationInMinutes", extendedBolus.durationInMinutes)
put("isDeletion", deleted) // put("isDeletion", deleted)
} // }
// @JvmOverloads // @JvmOverloads
// fun enqueueProfileSwitch(profileSwitch: ProfileSwitch, deleted: Boolean = false) = insertQueueItem("ProfileSwitches") { // fun enqueueProfileSwitch(profileSwitch: ProfileSwitch, deleted: Boolean = false) = insertQueueItem("ProfileSwitches") {
@ -244,22 +244,22 @@ class OpenHumansUploader @Inject constructor(
// put("double", tdd.total) // put("double", tdd.total)
// } // }
@JvmOverloads // @JvmOverloads
fun enqueueTemporaryBasal(temporaryBasal: TemporaryBasal?, deleted: Boolean = false) = temporaryBasal?.let { // fun enqueueTemporaryBasal(temporaryBasal: TemporaryBasal?, deleted: Boolean = false) = temporaryBasal?.let {
insertQueueItem("TemporaryBasals") { // insertQueueItem("TemporaryBasals") {
put("date", temporaryBasal.date) // put("date", temporaryBasal.date)
put("isValid", temporaryBasal.isValid) // put("isValid", temporaryBasal.isValid)
put("source", temporaryBasal.source) // put("source", temporaryBasal.source)
put("nsId", temporaryBasal._id) // put("nsId", temporaryBasal._id)
put("pumpId", temporaryBasal.pumpId) // put("pumpId", temporaryBasal.pumpId)
put("durationInMinutes", temporaryBasal.durationInMinutes) // put("durationInMinutes", temporaryBasal.durationInMinutes)
put("durationInMinutes", temporaryBasal.durationInMinutes) // put("durationInMinutes", temporaryBasal.durationInMinutes)
put("isAbsolute", temporaryBasal.isAbsolute) // put("isAbsolute", temporaryBasal.isAbsolute)
put("percentRate", temporaryBasal.percentRate) // put("percentRate", temporaryBasal.percentRate)
put("absoluteRate", temporaryBasal.absoluteRate) // put("absoluteRate", temporaryBasal.absoluteRate)
put("isDeletion", deleted) // put("isDeletion", deleted)
} // }
} // }
@JvmOverloads @JvmOverloads
fun enqueueTempTarget(tempTarget: TemporaryTarget?, deleted: Boolean = false) = tempTarget?.let { fun enqueueTempTarget(tempTarget: TemporaryTarget?, deleted: Boolean = false) = tempTarget?.let {
@ -439,6 +439,7 @@ class OpenHumansUploader @Inject constructor(
notificationManager.notify(FAILURE_NOTIFICATION_ID, notification) notificationManager.notify(FAILURE_NOTIFICATION_ID, notification)
} }
@kotlin.ExperimentalStdlibApi
fun uploadDataSegmentally(): Completable = fun uploadDataSegmentally(): Completable =
uploadData(UPLOAD_SEGMENT_SIZE) uploadData(UPLOAD_SEGMENT_SIZE)
.repeatUntil { databaseHelper.getOHQueueSize() == 0L } .repeatUntil { databaseHelper.getOHQueueSize() == 0L }
@ -452,6 +453,7 @@ class OpenHumansUploader @Inject constructor(
aapsLogger.error(LTag.OHUPLOADER, "Segmental upload exceptional", it) aapsLogger.error(LTag.OHUPLOADER, "Segmental upload exceptional", it)
} }
@kotlin.ExperimentalStdlibApi
@Suppress("SameParameterValue") @Suppress("SameParameterValue")
private fun uploadData(maxEntries: Long): Completable = gatherData(maxEntries) private fun uploadData(maxEntries: Long): Completable = gatherData(maxEntries)
.flatMap { data -> refreshAccessTokensIfNeeded().map { accessToken -> accessToken to data } } .flatMap { data -> refreshAccessTokensIfNeeded().map { accessToken -> accessToken to data } }
@ -494,6 +496,7 @@ class OpenHumansUploader @Inject constructor(
} }
} }
@kotlin.ExperimentalStdlibApi
private fun gatherData(maxEntries: Long) = Single.defer { private fun gatherData(maxEntries: Long) = Single.defer {
val items = databaseHelper.getAllOHQueueItems(maxEntries) val items = databaseHelper.getAllOHQueueItems(maxEntries)
val baos = ByteArrayOutputStream() val baos = ByteArrayOutputStream()

View file

@ -0,0 +1,811 @@
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.*
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.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.*
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 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 {
TIME,
CALC_PROGRESS,
PROFILE,
TEMPORARY_BASAL,
EXTENDED_BOLUS,
TEMPORARY_TARGET,
BG,
IOB_COB,
SENSITIVITY,
GRAPH
}
var rangeToDisplay = 6 // for graph
var toTime: Long = 0
var fromTime: Long = 0
var endTime: Long = 0
fun initRange() {
rangeToDisplay = sp.getInt(R.string.key_rangetodisplay, 6)
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)
}
toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific
fromTime = toTime - T.hours(rangeToDisplay.toLong()).msecs()
endTime = toTime
}
/*
* PROFILE
*/
var profile: Profile? = null
var profileName: String? = null
var profileNameWithRemainingTime: String? = null
val profileBackgroundColor: Int
get() =
profile?.let { profile ->
if (profile.percentage != 100 || profile.timeshift != 0) resourceHelper.gc(R.color.ribbonWarning)
else resourceHelper.gc(R.color.ribbonDefault)
} ?: resourceHelper.gc(R.color.ribbonTextDefault)
val profileTextColor: Int
get() =
profile?.let { profile ->
if (profile.percentage != 100 || profile.timeshift != 0) resourceHelper.gc(R.color.ribbonTextWarning)
else resourceHelper.gc(R.color.ribbonTextDefault)
} ?: resourceHelper.gc(R.color.ribbonTextDefault)
/*
* CALC PROGRESS
*/
var calcProgress: String = ""
/*
* BG
*/
var lastBg: GlucoseValue? = null
val lastBgColor: Int
get() = lastBg?.let { lastBg ->
when {
lastBg.valueToUnits(profileFunction.getUnits()) < defaultValueHelper.determineLowLine() -> resourceHelper.gc(R.color.low)
lastBg.valueToUnits(profileFunction.getUnits()) > defaultValueHelper.determineHighLine() -> resourceHelper.gc(R.color.high)
else -> resourceHelper.gc(R.color.inrange)
}
} ?: resourceHelper.gc(R.color.inrange)
val isActualBg: Boolean
get() =
lastBg?.let { lastBg ->
lastBg.timestamp > dateUtil.now() - T.mins(9).msecs()
} ?: false
/*
* TEMPORARY BASAL
*/
var temporaryBasal: TemporaryBasal? = null
val temporaryBasalText: String
get() =
profile?.let { profile ->
if (temporaryBasal?.isInProgress == false) temporaryBasal = null
temporaryBasal?.let { "T:" + it.toStringShort() }
?: resourceHelper.gs(R.string.pump_basebasalrate, profile.getBasal())
} ?: resourceHelper.gs(R.string.notavailable)
val temporaryBasalDialogText: String
get() = profile?.let { profile ->
temporaryBasal?.let { temporaryBasal ->
"${resourceHelper.gs(R.string.basebasalrate_label)}: ${resourceHelper.gs(R.string.pump_basebasalrate, profile.getBasal())}" +
"\n" + resourceHelper.gs(R.string.tempbasal_label) + ": " + temporaryBasal.toStringFull(profile, dateUtil)
}
?: "${resourceHelper.gs(R.string.basebasalrate_label)}: ${resourceHelper.gs(R.string.pump_basebasalrate, profile.getBasal())}"
} ?: resourceHelper.gs(R.string.notavailable)
val temporaryBasalIcon: Int
get() =
profile?.let { profile ->
temporaryBasal?.let { temporaryBasal ->
val percentRate = temporaryBasal.convertedToPercent(dateUtil.now(), profile)
when {
percentRate > 100 -> R.drawable.ic_cp_basal_tbr_high
percentRate < 100 -> R.drawable.ic_cp_basal_tbr_low
else -> R.drawable.ic_cp_basal_no_tbr
}
}
} ?: R.drawable.ic_cp_basal_no_tbr
val temporaryBasalColor: Int
get() = temporaryBasal?.let { resourceHelper.gc(R.color.basal) }
?: resourceHelper.gc(R.color.defaulttextcolor)
/*
* EXTENDED BOLUS
*/
var extendedBolus: ExtendedBolus? = null
val extendedBolusText: String
get() =
extendedBolus?.let { extendedBolus ->
if (!extendedBolus.isInProgress(dateUtil)) {
this@OverviewData.extendedBolus = null
""
} else if (activePlugin.activePump.isFakingTempsByExtendedBoluses) resourceHelper.gs(R.string.pump_basebasalrate, extendedBolus.rate)
else ""
} ?: ""
val extendedBolusDialogText: String
get() = extendedBolus?.toStringFull(dateUtil) ?: ""
/*
* IOB, COB
*/
var bolusIob: IobTotal? = null
var basalIob: IobTotal? = null
var cobInfo: CobInfo? = null
var lastCarbsTime: Long = 0L
val iobText: String
get() =
bolusIob?.let { bolusIob ->
basalIob?.let { basalIob ->
resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob)
} ?: resourceHelper.gs(R.string.value_unavailable_short)
} ?: resourceHelper.gs(R.string.value_unavailable_short)
val iobDialogText: String
get() =
bolusIob?.let { bolusIob ->
basalIob?.let { basalIob ->
resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) + "\n" +
resourceHelper.gs(R.string.bolus) + ": " + resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob) + "\n" +
resourceHelper.gs(R.string.basal) + ": " + resourceHelper.gs(R.string.formatinsulinunits, basalIob.basaliob)
} ?: resourceHelper.gs(R.string.value_unavailable_short)
} ?: resourceHelper.gs(R.string.value_unavailable_short)
/*
* TEMP TARGET
*/
var temporaryTarget: TemporaryTarget? = null
/*
* SENSITIVITY
*/
var lastAutosensData: AutosensData? = null
/*
* Graphs
*/
var bgReadingsArray: List<GlucoseValue> = ArrayList()
var maxBgValue = Double.MIN_VALUE
var bucketedGraphSeries: PointsWithLabelGraphSeries<DataPointWithLabelInterface> = PointsWithLabelGraphSeries()
var bgReadingGraphSeries: PointsWithLabelGraphSeries<DataPointWithLabelInterface> = PointsWithLabelGraphSeries()
var predictionsGraphSeries: PointsWithLabelGraphSeries<DataPointWithLabelInterface> = PointsWithLabelGraphSeries()
var maxBasalValueFound = 0.0
val basalScale = Scale()
var baseBasalGraphSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
var tempBasalGraphSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
var basalLineGraphSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
var absoluteBasalGraphSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
var temporaryTargetSeries: LineGraphSeries<DataPoint> = LineGraphSeries()
var maxIAValue = 0.0
val actScale = Scale()
var activitySeries: FixedLineGraphSeries<ScaledDataPoint> = FixedLineGraphSeries()
var activityPredictionSeries: FixedLineGraphSeries<ScaledDataPoint> = FixedLineGraphSeries()
var maxTreatmentsValue = 0.0
var treatmentsSeries: PointsWithLabelGraphSeries<DataPointWithLabelInterface> = PointsWithLabelGraphSeries()
var maxIobValueFound = Double.MIN_VALUE
val iobScale = Scale()
var iobSeries: FixedLineGraphSeries<ScaledDataPoint> = FixedLineGraphSeries()
var absIobSeries: FixedLineGraphSeries<ScaledDataPoint> = FixedLineGraphSeries()
var iobPredictions1Series: PointsWithLabelGraphSeries<DataPointWithLabelInterface> = PointsWithLabelGraphSeries()
var iobPredictions2Series: PointsWithLabelGraphSeries<DataPointWithLabelInterface> = PointsWithLabelGraphSeries()
var maxBGIValue = Double.MIN_VALUE
val bgiScale = Scale()
var minusBgiSeries: FixedLineGraphSeries<ScaledDataPoint> = FixedLineGraphSeries()
var minusBgiHistSeries: FixedLineGraphSeries<ScaledDataPoint> = FixedLineGraphSeries()
var maxCobValueFound = Double.MIN_VALUE
val cobScale = Scale()
var cobSeries: FixedLineGraphSeries<ScaledDataPoint> = FixedLineGraphSeries()
var cobMinFailOverSeries: PointsWithLabelGraphSeries<DataPointWithLabelInterface> = PointsWithLabelGraphSeries()
var maxDevValueFound = Double.MIN_VALUE
val devScale = Scale()
var deviationsSeries: BarGraphSeries<OverviewPlugin.DeviationDataPoint> = BarGraphSeries()
var maxRatioValueFound = 5.0 //even if sens data equals 0 for all the period, minimum scale is between 95% and 105%
var minRatioValueFound = -maxRatioValueFound
val ratioScale = Scale()
var ratioSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
var maxFromMaxValueFound = Double.MIN_VALUE
var maxFromMinValueFound = Double.MIN_VALUE
val dsMaxScale = Scale()
val dsMinScale = Scale()
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)
// OfflineEvent
repository.getOfflineEventDataFromTimeToTime(fromTime, endTime, true).blockingGet()
.map { TherapyEventDataPoint(TherapyEvent(timestamp = it.timestamp, duration = it.duration, type = TherapyEvent.Type.APS_OFFLINE, glucoseUnit = TherapyEvent.GlucoseUnit.MMOL), resourceHelper, profileFunction, translator) }
.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

@ -10,6 +10,7 @@ import android.graphics.Paint
import android.graphics.drawable.AnimationDrawable import android.graphics.drawable.AnimationDrawable
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.HandlerThread
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -19,25 +20,27 @@ import android.widget.LinearLayout
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.TextView import android.widget.TextView
import androidx.core.text.toSpanned import androidx.core.text.toSpanned
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.jjoe64.graphview.GraphView import com.jjoe64.graphview.GraphView
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.Constants import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.TemporaryTarget
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.interfaces.end import info.nightscout.androidaps.database.interfaces.end
import info.nightscout.androidaps.databinding.OverviewFragmentBinding import info.nightscout.androidaps.databinding.OverviewFragmentBinding
import info.nightscout.androidaps.dialogs.* import info.nightscout.androidaps.dialogs.*
import info.nightscout.androidaps.events.* import info.nightscout.androidaps.events.EventAcceptOpenLoopChange
import info.nightscout.androidaps.extensions.* import info.nightscout.androidaps.events.EventInitializationChanged
import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.events.EventPumpStatusChanged
import info.nightscout.androidaps.events.EventRefreshOverview
import info.nightscout.androidaps.extensions.directionToIcon
import info.nightscout.androidaps.extensions.isInProgress
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.extensions.valueToUnitsString
import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.logging.UserEntryLogger
@ -47,22 +50,19 @@ import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity
import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverview
import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.GlucoseValueDataPoint
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore
import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
import info.nightscout.androidaps.plugins.source.DexcomPlugin import info.nightscout.androidaps.plugins.source.DexcomPlugin
import info.nightscout.androidaps.plugins.source.XdripPlugin import info.nightscout.androidaps.plugins.source.XdripPlugin
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import info.nightscout.androidaps.queue.CommandQueue import info.nightscout.androidaps.queue.CommandQueue
import info.nightscout.androidaps.skins.SkinProvider import info.nightscout.androidaps.skins.SkinProvider
import info.nightscout.androidaps.utils.* import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.extensions.*
import info.nightscout.androidaps.utils.protection.ProtectionCheck import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.rx.AapsSchedulers
@ -70,15 +70,11 @@ import info.nightscout.androidaps.utils.sharedPreferences.SP
import info.nightscout.androidaps.utils.ui.UIRunnable import info.nightscout.androidaps.utils.ui.UIRunnable
import info.nightscout.androidaps.utils.wizard.QuickWizard import info.nightscout.androidaps.utils.wizard.QuickWizard
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import kotlinx.coroutines.Dispatchers import io.reactivex.rxkotlin.plusAssign
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min import kotlin.math.min
class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickListener { class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickListener {
@ -96,7 +92,6 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
@Inject lateinit var nsDeviceStatus: NSDeviceStatus @Inject lateinit var nsDeviceStatus: NSDeviceStatus
@Inject lateinit var loopPlugin: LoopPlugin @Inject lateinit var loopPlugin: LoopPlugin
@Inject lateinit var activePlugin: ActivePlugin @Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var treatmentsPlugin: TreatmentsPlugin
@Inject lateinit var iobCobCalculator: IobCobCalculator @Inject lateinit var iobCobCalculator: IobCobCalculator
@Inject lateinit var dexcomPlugin: DexcomPlugin @Inject lateinit var dexcomPlugin: DexcomPlugin
@Inject lateinit var dexcomMediator: DexcomPlugin.DexcomMediator @Inject lateinit var dexcomMediator: DexcomPlugin.DexcomMediator
@ -112,10 +107,11 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
@Inject lateinit var trendCalculator: TrendCalculator @Inject lateinit var trendCalculator: TrendCalculator
@Inject lateinit var config: Config @Inject lateinit var config: Config
@Inject lateinit var dateUtil: DateUtil @Inject lateinit var dateUtil: DateUtil
@Inject lateinit var databaseHelper: DatabaseHelperInterface
@Inject lateinit var uel: UserEntryLogger @Inject lateinit var uel: UserEntryLogger
@Inject lateinit var repository: AppRepository @Inject lateinit var repository: AppRepository
@Inject lateinit var glucoseStatusProvider: GlucoseStatusProvider @Inject lateinit var glucoseStatusProvider: GlucoseStatusProvider
@Inject lateinit var overviewData: OverviewData
@Inject lateinit var overviewPlugin: OverviewPlugin
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
@ -123,17 +119,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
private var smallHeight = false private var smallHeight = false
private lateinit var dm: DisplayMetrics private lateinit var dm: DisplayMetrics
private var axisWidth: Int = 0 private var axisWidth: Int = 0
private var rangeToDisplay = 6 // for graph
private var loopHandler = Handler()
private var refreshLoop: Runnable? = null private var refreshLoop: Runnable? = null
private lateinit var handler: Handler
private val secondaryGraphs = ArrayList<GraphView>() private val secondaryGraphs = ArrayList<GraphView>()
private val secondaryGraphsLabel = ArrayList<TextView>() private val secondaryGraphsLabel = ArrayList<TextView>()
private var carbAnimation: AnimationDrawable? = null private var carbAnimation: AnimationDrawable? = null
private val graphLock = Object()
private var _binding: OverviewFragmentBinding? = null private var _binding: OverviewFragmentBinding? = null
// This property is only valid between onCreateView and // This property is only valid between onCreateView and
@ -150,6 +143,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper)
// pre-process landscape mode // pre-process landscape mode
val screenWidth = dm.widthPixels val screenWidth = dm.widthPixels
@ -175,13 +169,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
carbAnimation?.setEnterFadeDuration(1200) carbAnimation?.setEnterFadeDuration(1200)
carbAnimation?.setExitFadeDuration(1200) carbAnimation?.setExitFadeDuration(1200)
rangeToDisplay = sp.getInt(R.string.key_rangetodisplay, 6)
binding.graphsLayout.bgGraph.setOnLongClickListener { binding.graphsLayout.bgGraph.setOnLongClickListener {
rangeToDisplay += 6 overviewData.rangeToDisplay += 6
rangeToDisplay = if (rangeToDisplay > 24) 6 else rangeToDisplay overviewData.rangeToDisplay = if (overviewData.rangeToDisplay > 24) 6 else overviewData.rangeToDisplay
sp.putInt(R.string.key_rangetodisplay, rangeToDisplay) sp.putInt(R.string.key_rangetodisplay, overviewData.rangeToDisplay)
updateGUI("rangeChange") overviewData.initRange()
updateGUI("rangeChange", OverviewData.Property.GRAPH)
rxBus.send(EventPreferenceChange(resourceHelper, R.string.key_rangetodisplay))
sp.putBoolean(R.string.key_objectiveusescale, true) sp.putBoolean(R.string.key_objectiveusescale, true)
false false
} }
@ -210,55 +205,32 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
disposable.clear() disposable.clear()
loopHandler.removeCallbacksAndMessages(null) handler.removeCallbacksAndMessages(null)
} }
@Synchronized @Synchronized
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
disposable += activePlugin.activeOverview.overviewBus
.toObservable(EventUpdateOverview::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ updateGUI(it.from, it.what) }, fabricPrivacy::logException)
disposable.add(rxBus disposable.add(rxBus
.toObservable(EventRefreshOverview::class.java) .toObservable(EventRefreshOverview::class.java)
.observeOn(aapsSchedulers.main) .observeOn(aapsSchedulers.io)
.subscribe({ .subscribe({
if (it.now) updateGUI(it.from) if (it.now) overviewPlugin.refreshLoop(it.from)
else scheduleUpdateGUI(it.from) else scheduleUpdateGUI(it.from)
}, fabricPrivacy::logException)) }, fabricPrivacy::logException))
disposable.add(rxBus
.toObservable(EventExtendedBolusChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ scheduleUpdateGUI("EventExtendedBolusChange") }, fabricPrivacy::logException))
disposable.add(rxBus
.toObservable(EventTempBasalChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ scheduleUpdateGUI("EventTempBasalChange") }, fabricPrivacy::logException))
disposable.add(rxBus
.toObservable(EventTreatmentChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ scheduleUpdateGUI("EventTreatmentChange") }, fabricPrivacy::logException))
disposable.add(rxBus
.toObservable(EventTempTargetChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ scheduleUpdateGUI("EventTempTargetChange") }, fabricPrivacy::logException))
disposable.add(rxBus disposable.add(rxBus
.toObservable(EventAcceptOpenLoopChange::class.java) .toObservable(EventAcceptOpenLoopChange::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)
.subscribe({ scheduleUpdateGUI("EventAcceptOpenLoopChange") }, fabricPrivacy::logException)) .subscribe({ scheduleUpdateGUI("EventAcceptOpenLoopChange") }, fabricPrivacy::logException))
disposable.add(rxBus
.toObservable(EventTherapyEventChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ scheduleUpdateGUI("EventCareportalEventChange") }, fabricPrivacy::logException))
disposable.add(rxBus disposable.add(rxBus
.toObservable(EventInitializationChanged::class.java) .toObservable(EventInitializationChanged::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.main)
.subscribe({ scheduleUpdateGUI("EventInitializationChanged") }, fabricPrivacy::logException)) .subscribe({ updateGUI("EventInitializationChanged", OverviewData.Property.TIME) }, fabricPrivacy::logException))
disposable.add(rxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ scheduleUpdateGUI("EventAutosensCalculationFinished") }, fabricPrivacy::logException))
disposable.add(rxBus
.toObservable(EventProfileSwitchChanged::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ scheduleUpdateGUI("EventProfileNeedsUpdate") }, fabricPrivacy::logException))
disposable.add(rxBus disposable.add(rxBus
.toObservable(EventPreferenceChange::class.java) .toObservable(EventPreferenceChange::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)
@ -271,18 +243,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
.toObservable(EventPumpStatusChanged::class.java) .toObservable(EventPumpStatusChanged::class.java)
.observeOn(aapsSchedulers.main) .observeOn(aapsSchedulers.main)
.subscribe({ updatePumpStatus(it) }, fabricPrivacy::logException)) .subscribe({ updatePumpStatus(it) }, fabricPrivacy::logException))
disposable.add(rxBus
.toObservable(EventIobCalculationProgress::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ binding.graphsLayout.iobCalculationProgress.text = it.progress }, fabricPrivacy::logException))
refreshLoop = Runnable { refreshLoop = Runnable {
scheduleUpdateGUI("refreshLoop") overviewPlugin.refreshLoop("refreshLoop")
loopHandler.postDelayed(refreshLoop, 60 * 1000L) handler.postDelayed(refreshLoop, 60 * 1000L)
} }
loopHandler.postDelayed(refreshLoop, 60 * 1000L) handler.postDelayed(refreshLoop, 60 * 1000L)
updateGUI("onResume") for (p in OverviewData.Property.values()) updateGUI("onResume", p)
} }
@Synchronized @Synchronized
@ -342,6 +310,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
R.id.accept_temp_button -> { R.id.accept_temp_button -> {
profileFunction.getProfile() ?: return profileFunction.getProfile() ?: return
if (loopPlugin.isEnabled(PluginType.LOOP)) { if (loopPlugin.isEnabled(PluginType.LOOP)) {
handler.post {
val lastRun = loopPlugin.lastRun val lastRun = loopPlugin.lastRun
loopPlugin.invoke("Accept temp button", false) loopPlugin.invoke("Accept temp button", false)
if (lastRun?.lastAPSRun != null && lastRun.constraintsProcessed?.isChangeRequested == true) { if (lastRun?.lastAPSRun != null && lastRun.constraintsProcessed?.isChangeRequested == true) {
@ -352,12 +321,13 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
binding.buttonsLayout.acceptTempButton.visibility = View.GONE binding.buttonsLayout.acceptTempButton.visibility = View.GONE
(context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(Constants.notificationID) (context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(Constants.notificationID)
rxBus.send(EventWearInitiateAction("cancelChangeRequest")) rxBus.send(EventWearInitiateAction("cancelChangeRequest"))
loopPlugin.acceptChangeRequest() Thread { loopPlugin.acceptChangeRequest() }.run()
}) })
}) })
} }
} }
} }
}
R.id.aps_mode -> { R.id.aps_mode -> {
protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable {
@ -490,133 +460,11 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
} }
private fun prepareGraphsIfNeeded(numOfGraphs: Int) { private fun processAps() {
synchronized(graphLock) {
if (numOfGraphs != secondaryGraphs.size - 1) {
//aapsLogger.debug("New secondary graph count ${numOfGraphs-1}")
// rebuild needed
secondaryGraphs.clear()
secondaryGraphsLabel.clear()
binding.graphsLayout.iobGraph.removeAllViews()
for (i in 1 until numOfGraphs) {
val relativeLayout = RelativeLayout(context)
relativeLayout.layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
val graph = GraphView(context)
graph.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, resourceHelper.dpToPx(skinProvider.activeSkin().secondaryGraphHeight)).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(context)
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.graphsLayout.iobGraph.addView(relativeLayout)
secondaryGraphs.add(graph)
}
}
}
}
var task: Runnable? = null
private fun scheduleUpdateGUI(from: String) {
class UpdateRunnable : Runnable {
override fun run() {
updateGUI(from)
task = null
}
}
view?.removeCallbacks(task)
task = UpdateRunnable()
view?.postDelayed(task, 500)
}
@SuppressLint("SetTextI18n")
fun updateGUI(from: String) {
if (_binding == null) return
aapsLogger.debug("UpdateGUI from $from")
binding.infoLayout.time.text = dateUtil.timeString(dateUtil.now())
if (!profileFunction.isProfileValid("Overview")) {
binding.loopPumpStatusLayout.pumpStatus.setText(R.string.noprofileset)
binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.VISIBLE
binding.loopPumpStatusLayout.loopLayout.visibility = View.GONE
return
}
binding.notifications.let { notificationStore.updateNotifications(it) }
binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.GONE
binding.loopPumpStatusLayout.loopLayout.visibility = View.VISIBLE
val profile = profileFunction.getProfile() ?: return
val actualBG = iobCobCalculator.ads.actualBg()
val lastBG = iobCobCalculator.ads.lastBg()
val pump = activePlugin.activePump val pump = activePlugin.activePump
val units = profileFunction.getUnits()
val lowLine = defaultValueHelper.determineLowLine()
val highLine = defaultValueHelper.determineHighLine()
val lastRun = loopPlugin.lastRun
val predictionsAvailable = if (config.APS) lastRun?.request?.hasPredictions == true else config.NSCLIENT
try {
updateGraph(lastRun, predictionsAvailable, lowLine, highLine, pump, profile)
} catch (e: IllegalStateException) {
return // view no longer exists
}
//Start with updating the BG as it is unaffected by loop.
// **** BG value ****
if (lastBG != null) {
val color = when {
lastBG.valueToUnits(units) < lowLine -> resourceHelper.gc(R.color.low)
lastBG.valueToUnits(units) > highLine -> resourceHelper.gc(R.color.high)
else -> resourceHelper.gc(R.color.inrange)
}
binding.infoLayout.bg.text = lastBG.valueToUnitsString(units)
binding.infoLayout.bg.setTextColor(color)
binding.infoLayout.arrow.setImageResource(trendCalculator.getTrendArrow(lastBG).directionToIcon())
binding.infoLayout.arrow.setColorFilter(color)
val glucoseStatus = glucoseStatusProvider.glucoseStatusData
if (glucoseStatus != null) {
binding.infoLayout.deltaLarge.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
binding.infoLayout.deltaLarge.setTextColor(color)
binding.infoLayout.delta.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
binding.infoLayout.avgDelta.text = Profile.toSignedUnitsString(glucoseStatus.shortAvgDelta, glucoseStatus.shortAvgDelta * Constants.MGDL_TO_MMOLL, units)
binding.infoLayout.longAvgDelta.text = Profile.toSignedUnitsString(glucoseStatus.longAvgDelta, glucoseStatus.longAvgDelta * Constants.MGDL_TO_MMOLL, units)
} else {
binding.infoLayout.delta.text = "Δ " + resourceHelper.gs(R.string.notavailable)
binding.infoLayout.avgDelta.text = ""
binding.infoLayout.longAvgDelta.text = ""
}
// strike through if BG is old
binding.infoLayout.bg.let { overview_bg ->
var flag = overview_bg.paintFlags
flag = if (actualBG == null) {
flag or Paint.STRIKE_THRU_TEXT_FLAG
} else flag and Paint.STRIKE_THRU_TEXT_FLAG.inv()
overview_bg.paintFlags = flag
}
binding.infoLayout.timeAgo.text = dateUtil.minAgo(resourceHelper, lastBG.timestamp)
binding.infoLayout.timeAgoShort.text = "(" + dateUtil.minAgoShort(lastBG.timestamp) + ")"
}
val closedLoopEnabled = constraintChecker.isClosedLoopAllowed()
// aps mode // aps mode
val closedLoopEnabled = constraintChecker.isClosedLoopAllowed()
if (config.APS && pump.pumpDescription.isTempBasalCapable) { if (config.APS && pump.pumpDescription.isTempBasalCapable) {
binding.infoLayout.apsMode.visibility = View.VISIBLE binding.infoLayout.apsMode.visibility = View.VISIBLE
binding.infoLayout.timeLayout.visibility = View.GONE binding.infoLayout.timeLayout.visibility = View.GONE
@ -677,15 +525,194 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
binding.infoLayout.timeLayout.visibility = View.VISIBLE binding.infoLayout.timeLayout.visibility = View.VISIBLE
} }
// pump status from ns
binding.pump.text = nsDeviceStatus.pumpStatus
binding.pump.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.pump), nsDeviceStatus.extendedPumpStatus) } }
// OpenAPS status from ns
binding.openaps.text = nsDeviceStatus.openApsStatus
binding.openaps.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.openaps), nsDeviceStatus.extendedOpenApsStatus) } }
// Uploader status from ns
binding.uploader.text = nsDeviceStatus.uploaderStatusSpanned
binding.uploader.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.uploader), nsDeviceStatus.extendedUploaderStatus) } }
}
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.graphsLayout.iobGraph.removeAllViews()
for (i in 1 until numOfGraphs) {
val relativeLayout = RelativeLayout(context)
relativeLayout.layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
val graph = GraphView(context)
graph.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, resourceHelper.dpToPx(skinProvider.activeSkin().secondaryGraphHeight)).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(context)
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.graphsLayout.iobGraph.addView(relativeLayout)
secondaryGraphs.add(graph)
}
}
}
var task: Runnable? = null
private fun scheduleUpdateGUI(from: String) {
class UpdateRunnable : Runnable {
override fun run() {
overviewPlugin.refreshLoop(from)
task = null
}
}
handler.removeCallbacks(task)
task = UpdateRunnable()
handler.postDelayed(task, 500)
}
@Suppress("UNUSED_PARAMETER")
@SuppressLint("SetTextI18n")
fun updateGUI(from: String, what: OverviewData.Property) {
// if (what != OverviewData.Property.CALC_PROGRESS)
// aapsLogger.debug(LTag.UI, "UpdateGui $from $what")
if (overviewData.profile == null) {
binding.loopPumpStatusLayout.pumpStatus.setText(R.string.noprofileset)
binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.VISIBLE
binding.loopPumpStatusLayout.loopLayout.visibility = View.GONE
return
}
binding.notifications.let { notificationStore.updateNotifications(it) }
binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.GONE
binding.loopPumpStatusLayout.loopLayout.visibility = View.VISIBLE
val units = profileFunction.getUnits()
val pump = activePlugin.activePump
when (what) {
OverviewData.Property.BG -> {
binding.infoLayout.bg.text = overviewData.lastBg?.valueToUnitsString(units)
?: resourceHelper.gs(R.string.notavailable)
binding.infoLayout.bg.setTextColor(overviewData.lastBgColor)
binding.infoLayout.arrow.setImageResource(trendCalculator.getTrendArrow(overviewData.lastBg).directionToIcon())
binding.infoLayout.arrow.setColorFilter(overviewData.lastBgColor)
val glucoseStatus = glucoseStatusProvider.glucoseStatusData
if (glucoseStatus != null) {
binding.infoLayout.deltaLarge.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
binding.infoLayout.deltaLarge.setTextColor(overviewData.lastBgColor)
binding.infoLayout.delta.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
binding.infoLayout.avgDelta.text = Profile.toSignedUnitsString(glucoseStatus.shortAvgDelta, glucoseStatus.shortAvgDelta * Constants.MGDL_TO_MMOLL, units)
binding.infoLayout.longAvgDelta.text = Profile.toSignedUnitsString(glucoseStatus.longAvgDelta, glucoseStatus.longAvgDelta * Constants.MGDL_TO_MMOLL, units)
} else {
binding.infoLayout.deltaLarge.text = ""
binding.infoLayout.delta.text = "Δ " + resourceHelper.gs(R.string.notavailable)
binding.infoLayout.avgDelta.text = ""
binding.infoLayout.longAvgDelta.text = ""
}
// strike through if BG is old
binding.infoLayout.bg.paintFlags =
if (!overviewData.isActualBg) binding.infoLayout.bg.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else binding.infoLayout.bg.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
binding.infoLayout.timeAgo.text = dateUtil.minAgo(resourceHelper, overviewData.lastBg?.timestamp)
binding.infoLayout.timeAgoShort.text = "(" + dateUtil.minAgoShort(overviewData.lastBg?.timestamp) + ")"
}
OverviewData.Property.PROFILE -> {
binding.loopPumpStatusLayout.activeProfile.text = overviewData.profileNameWithRemainingTime
?: ""
binding.loopPumpStatusLayout.activeProfile.setBackgroundColor(overviewData.profileBackgroundColor)
binding.loopPumpStatusLayout.activeProfile.setTextColor(overviewData.profileTextColor)
}
OverviewData.Property.TEMPORARY_BASAL -> {
binding.infoLayout.baseBasal.text = overviewData.temporaryBasalText
binding.infoLayout.baseBasal.setTextColor(overviewData.temporaryBasalColor)
binding.infoLayout.baseBasalIcon.setImageResource(overviewData.temporaryBasalIcon)
binding.infoLayout.basalLayout.setOnClickListener {
activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.basal), overviewData.temporaryBasalDialogText) }
}
}
OverviewData.Property.EXTENDED_BOLUS -> {
binding.infoLayout.extendedBolus.text = overviewData.extendedBolusText
binding.infoLayout.extendedBolus.setOnClickListener {
activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.extended_bolus), overviewData.extendedBolusDialogText) }
}
binding.infoLayout.extendedLayout.visibility = (overviewData.extendedBolus != null && !pump.isFakingTempsByExtendedBoluses).toVisibility()
}
OverviewData.Property.TIME -> {
binding.infoLayout.time.text = dateUtil.timeString(dateUtil.now())
// Status lights
binding.statusLightsLayout.statusLights.visibility = (sp.getBoolean(R.string.key_show_statuslights, true) || config.NSCLIENT).toVisibility()
statusLightHandler.updateStatusLights(binding.statusLightsLayout.cannulaAge, binding.statusLightsLayout.insulinAge, binding.statusLightsLayout.reservoirLevel, binding.statusLightsLayout.sensorAge, null, binding.statusLightsLayout.pbAge, binding.statusLightsLayout.batteryLevel)
processButtonsVisibility()
processAps()
}
OverviewData.Property.IOB_COB -> {
binding.infoLayout.iob.text = overviewData.iobText
binding.infoLayout.iobLayout.setOnClickListener {
activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.iob), overviewData.iobDialogText) }
}
// cob
var cobText: String = resourceHelper.gs(R.string.value_unavailable_short)
overviewData.cobInfo?.let { cobInfo ->
if (cobInfo.displayCob != null) {
cobText = resourceHelper.gs(R.string.format_carbs, cobInfo.displayCob!!.toInt())
if (cobInfo.futureCarbs > 0) cobText += "(" + DecimalFormatter.to0Decimal(cobInfo.futureCarbs) + ")"
}
}
binding.infoLayout.cob.text = cobText
val constraintsProcessed = loopPlugin.lastRun?.constraintsProcessed
val lastRun = loopPlugin.lastRun
if (config.APS && constraintsProcessed != null && lastRun != null) {
if (constraintsProcessed.carbsReq > 0) {
//only display carbsreq when carbs have not been entered recently
if (overviewData.lastCarbsTime < lastRun.lastAPSRun) {
cobText = cobText + " | " + constraintsProcessed.carbsReq + " " + resourceHelper.gs(R.string.required)
}
if (carbAnimation?.isRunning == false)
carbAnimation?.start()
} else {
carbAnimation?.stop()
carbAnimation?.selectDrawable(0)
}
}
}
OverviewData.Property.TEMPORARY_TARGET -> {
// temp target // temp target
val tempTarget: ValueWrapper<TemporaryTarget> = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() if (overviewData.temporaryTarget?.isInProgress(dateUtil) == false) overviewData.temporaryTarget = null
if (tempTarget is ValueWrapper.Existing) { val tempTarget = overviewData.temporaryTarget
if (tempTarget != null) {
binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning)) binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning))
binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning)) binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning))
binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(tempTarget.value.lowTarget, tempTarget.value.highTarget, GlucoseUnit.MGDL, units) + " " + dateUtil.untilString(tempTarget.value.end, resourceHelper) binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(tempTarget.lowTarget, tempTarget.highTarget, GlucoseUnit.MGDL, units) + " " + dateUtil.untilString(tempTarget.end, resourceHelper)
} else { } else {
// If the target is not the same as set in the profile then oref has overridden it // If the target is not the same as set in the profile then oref has overridden it
val targetUsed = lastRun?.constraintsProcessed?.targetBG ?: 0.0 overviewData.profile?.let { profile ->
val targetUsed = loopPlugin.lastRun?.constraintsProcessed?.targetBG ?: 0.0
if (targetUsed != 0.0 && abs(profile.getTargetMgdl() - targetUsed) > 0.01) { if (targetUsed != 0.0 && abs(profile.getTargetMgdl() - targetUsed) > 0.01) {
aapsLogger.debug("Adjusted target. Profile: ${profile.getTargetMgdl()} APS: $targetUsed") aapsLogger.debug("Adjusted target. Profile: ${profile.getTargetMgdl()} APS: $targetUsed")
@ -698,197 +725,36 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(profile.getTargetLowMgdl(), profile.getTargetHighMgdl(), GlucoseUnit.MGDL, units) binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(profile.getTargetLowMgdl(), profile.getTargetHighMgdl(), GlucoseUnit.MGDL, units)
} }
} }
// Basal, TBR
val activeTemp = iobCobCalculator.getTempBasalIncludingConvertedExtended(System.currentTimeMillis())
binding.infoLayout.baseBasal.text = activeTemp?.let { "T:" + activeTemp.toStringShort() }
?: resourceHelper.gs(R.string.pump_basebasalrate, profile.getBasal())
binding.infoLayout.basalLayout.setOnClickListener {
var fullText = "${resourceHelper.gs(R.string.basebasalrate_label)}: ${resourceHelper.gs(R.string.pump_basebasalrate, profile.getBasal())}"
if (activeTemp != null)
fullText += "\n" + resourceHelper.gs(R.string.tempbasal_label) + ": " + activeTemp.toStringFull(profile, dateUtil)
activity?.let {
OKDialog.show(it, resourceHelper.gs(R.string.basal), fullText)
}
}
binding.infoLayout.baseBasal.setTextColor(activeTemp?.let { resourceHelper.gc(R.color.basal) }
?: resourceHelper.gc(R.color.defaulttextcolor))
binding.infoLayout.baseBasalIcon.setImageResource(R.drawable.ic_cp_basal_no_tbr)
val percentRate = activeTemp?.convertedToPercent(System.currentTimeMillis(), profile)
?: 100
if (percentRate > 100) binding.infoLayout.baseBasalIcon.setImageResource(R.drawable.ic_cp_basal_tbr_high)
if (percentRate < 100) binding.infoLayout.baseBasalIcon.setImageResource(R.drawable.ic_cp_basal_tbr_low)
// Extended bolus
val extendedBolus = repository.getExtendedBolusActiveAt(dateUtil.now()).blockingGet()
binding.infoLayout.extendedBolus.text =
if (extendedBolus is ValueWrapper.Existing && !pump.isFakingTempsByExtendedBoluses)
resourceHelper.gs(R.string.pump_basebasalrate, extendedBolus.value.rate)
else ""
binding.infoLayout.extendedBolus.setOnClickListener {
if (extendedBolus is ValueWrapper.Existing) activity?.let {
OKDialog.show(it, resourceHelper.gs(R.string.extended_bolus), extendedBolus.value.toStringFull(dateUtil))
}
}
binding.infoLayout.extendedLayout.visibility = (extendedBolus is ValueWrapper.Existing && !pump.isFakingTempsByExtendedBoluses).toVisibility()
// Active profile
binding.loopPumpStatusLayout.activeProfile.text = profileFunction.getProfileNameWithRemainingTime()
if (profile.percentage != 100 || profile.timeshift != 0) {
binding.loopPumpStatusLayout.activeProfile.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning))
binding.loopPumpStatusLayout.activeProfile.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning))
} else {
binding.loopPumpStatusLayout.activeProfile.setBackgroundColor(resourceHelper.gc(R.color.ribbonDefault))
binding.loopPumpStatusLayout.activeProfile.setTextColor(resourceHelper.gc(R.color.ribbonTextDefault))
}
processButtonsVisibility()
// iob
val bolusIob = iobCobCalculator.calculateIobFromBolus().round()
val basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
binding.infoLayout.iob.text = resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob)
binding.infoLayout.iobLayout.setOnClickListener {
activity?.let {
OKDialog.show(it, resourceHelper.gs(R.string.iob),
resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) + "\n" +
resourceHelper.gs(R.string.bolus) + ": " + resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob) + "\n" +
resourceHelper.gs(R.string.basal) + ": " + resourceHelper.gs(R.string.formatinsulinunits, basalIob.basaliob)
)
} }
} }
// Status lights OverviewData.Property.GRAPH -> {
binding.statusLightsLayout.statusLights.visibility = (sp.getBoolean(R.string.key_show_statuslights, true) || config.NSCLIENT).toVisibility() val graphData = GraphData(injector, binding.graphsLayout.bgGraph, overviewData)
statusLightHandler.updateStatusLights(binding.statusLightsLayout.cannulaAge, binding.statusLightsLayout.insulinAge, binding.statusLightsLayout.reservoirLevel, binding.statusLightsLayout.sensorAge, null, binding.statusLightsLayout.pbAge, binding.statusLightsLayout.batteryLevel)
// cob
var cobText: String = resourceHelper.gs(R.string.value_unavailable_short)
val cobInfo = iobCobCalculator.getCobInfo(false, "Overview COB")
if (cobInfo.displayCob != null) {
cobText = resourceHelper.gs(R.string.format_carbs, cobInfo.displayCob!!.toInt())
if (cobInfo.futureCarbs > 0) cobText += "(" + DecimalFormatter.to0Decimal(cobInfo.futureCarbs) + ")"
}
if (config.APS && lastRun?.constraintsProcessed != null) {
if (lastRun.constraintsProcessed!!.carbsReq > 0) {
//only display carbsreq when carbs have not been entered recently
val lastCarb = repository.getLastCarbsRecordWrapped().blockingGet()
val lastCarbsTime = if (lastCarb is ValueWrapper.Existing) lastCarb.value.timestamp else 0L
if (lastCarbsTime < lastRun.lastAPSRun) {
cobText = cobText + " | " + lastRun.constraintsProcessed!!.carbsReq + " " + resourceHelper.gs(R.string.required)
}
binding.infoLayout.cob.text = cobText
if (carbAnimation?.isRunning == false)
carbAnimation?.start()
} else {
binding.infoLayout.cob.text = cobText
carbAnimation?.stop()
carbAnimation?.selectDrawable(0)
}
} else binding.infoLayout.cob.text = cobText
// pump status from ns
binding.pump.text = nsDeviceStatus.pumpStatus
binding.pump.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.pump), nsDeviceStatus.extendedPumpStatus) } }
// OpenAPS status from ns
binding.openaps.text = nsDeviceStatus.openApsStatus
binding.openaps.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.openaps), nsDeviceStatus.extendedOpenApsStatus) } }
// Uploader status from ns
binding.uploader.text = nsDeviceStatus.uploaderStatusSpanned
binding.uploader.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.uploader), nsDeviceStatus.extendedUploaderStatus) } }
// Sensitivity
if (sp.getBoolean(R.string.key_openapsama_useautosens, false) && constraintChecker.isAutosensModeEnabled().value()) {
binding.infoLayout.sensitivityIcon.setImageResource(R.drawable.ic_swap_vert_black_48dp_green)
} else {
binding.infoLayout.sensitivityIcon.setImageResource(R.drawable.ic_x_swap_vert)
}
binding.infoLayout.sensitivity.text =
iobCobCalculator.ads.getLastAutosensData("Overview", aapsLogger, dateUtil)?.let { autosensData ->
String.format(Locale.ENGLISH, "%.0f%%", autosensData.autosensResult.ratio * 100)
} ?: ""
}
private fun updateGraph(lastRun: LoopInterface.LastRun?, predictionsAvailable: Boolean, lowLine: Double, highLine: Double, pump: Pump, profile: Profile) {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
if (_binding == null) return@launch
val menuChartSettings = overviewMenus.setting val menuChartSettings = overviewMenus.setting
prepareGraphsIfNeeded(menuChartSettings.size) graphData.addInRangeArea(overviewData.fromTime, overviewData.endTime, defaultValueHelper.determineLowLine(), defaultValueHelper.determineHighLine())
val graphData = GraphData(injector, binding.graphsLayout.bgGraph, iobCobCalculator) graphData.addBgReadings(menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal])
val secondaryGraphsData: ArrayList<GraphData> = ArrayList() if (buildHelper.isDev()) graphData.addBucketedData()
graphData.addTreatments()
// do preparation in different thread if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal])
withContext(Dispatchers.Default) { graphData.addActivity(0.8)
// align to hours if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal])
val calendar = Calendar.getInstance() graphData.addBasals()
calendar.timeInMillis = System.currentTimeMillis() graphData.addTargetLine()
calendar[Calendar.MILLISECOND] = 0 graphData.addNowLine(dateUtil.now())
calendar[Calendar.SECOND] = 0
calendar[Calendar.MINUTE] = 0
calendar.add(Calendar.HOUR, 1)
val hoursToFetch: Int
val toTime: Long
val fromTime: Long
val endTime: Long
val apsResult = if (config.APS) lastRun?.constraintsProcessed else nsDeviceStatus.getAPSResult(injector)
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)
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 {
hoursToFetch = rangeToDisplay
toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific
fromTime = toTime - T.hours(hoursToFetch.toLong()).msecs()
endTime = toTime
}
val now = System.currentTimeMillis()
// ------------------ 1st graph
// **** In range Area ****
graphData.addInRangeArea(fromTime, endTime, lowLine, highLine)
// **** BG ****
if (predictionsAvailable && menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal])
graphData.addBgReadings(fromTime, toTime, highLine, apsResult?.predictions?.map { bg-> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper) }?.toMutableList())
else graphData.addBgReadings(fromTime, toTime, highLine, null)
if (buildHelper.isDev()) graphData.addBucketedData(fromTime, toTime)
// Treatments
graphData.addTreatments(fromTime, endTime)
// set manual x bounds to have nice steps // set manual x bounds to have nice steps
graphData.setNumVerticalLabels() graphData.setNumVerticalLabels()
graphData.formatAxis(fromTime, endTime) graphData.formatAxis(overviewData.fromTime, overviewData.endTime)
graphData.performUpdate()
if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal]) // 2nd graphs
graphData.addActivity(fromTime, endTime, false, 0.8) prepareGraphsIfNeeded(menuChartSettings.size)
val secondaryGraphsData: ArrayList<GraphData> = ArrayList()
// add basal data val now = System.currentTimeMillis()
if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal])
graphData.addBasals(fromTime, now, lowLine / graphData.maxY / 1.2)
// add target line
graphData.addTargetLine(fromTime, toTime, profile, loopPlugin.lastRun)
// **** NOW line ****
graphData.addNowLine(now)
// ------------------ 2nd graph
synchronized(graphLock) {
for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) { for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) {
val secondGraphData = GraphData(injector, secondaryGraphs[g], iobCobCalculator) val secondGraphData = GraphData(injector, secondaryGraphs[g], overviewData)
var useABSForScale = false var useABSForScale = false
var useIobForScale = false var useIobForScale = false
var useCobForScale = false var useCobForScale = false
@ -905,27 +771,21 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = 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] 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, now, useABSForScale, 1.0) if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(useABSForScale, 1.0)
if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(fromTime, now, useIobForScale, 1.0, menuChartSettings[g + 1][OverviewMenus.CharType.PRE.ordinal], alignIobScale) if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(useIobForScale, 1.0)
if (menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal]) secondGraphData.addCob(fromTime, now, useCobForScale, if (useCobForScale) 1.0 else 0.5) 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(fromTime, now, useDevForScale, 1.0, alignDevBgiScale) if (menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal]) secondGraphData.addDeviations(useDevForScale, 1.0)
if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(fromTime, endTime, useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8, alignDevBgiScale) 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(fromTime, now, useRatioForScale, if (useRatioForScale) 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(fromTime, now, useDSForScale, 1.0) if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && buildHelper.isDev()) secondGraphData.addDeviationSlope(useDSForScale, if(useDSForScale) 1.0 else 0.8, useRatioForScale)
// set manual x bounds to have nice steps // set manual x bounds to have nice steps
secondGraphData.formatAxis(fromTime, endTime) secondGraphData.formatAxis(overviewData.fromTime, overviewData.endTime)
secondGraphData.addNowLine(now) secondGraphData.addNowLine(now)
secondaryGraphsData.add(secondGraphData) secondaryGraphsData.add(secondGraphData)
} }
}
}
// finally enforce drawing of graphs in UI thread
graphData.performUpdate()
synchronized(graphLock) {
for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) { for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) {
secondaryGraphsLabel[g].text = overviewMenus.enabledTypes(g + 1) secondaryGraphsLabel[g].text = overviewMenus.enabledTypes(g + 1)
secondaryGraphs[g].visibility = ( secondaryGraphs[g].visibility = (
@ -940,6 +800,23 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
secondaryGraphsData[g].performUpdate() secondaryGraphsData[g].performUpdate()
} }
} }
OverviewData.Property.CALC_PROGRESS -> {
binding.graphsLayout.iobCalculationProgress.text = overviewData.calcProgress
}
OverviewData.Property.SENSITIVITY -> {
if (sp.getBoolean(R.string.key_openapsama_useautosens, false) && constraintChecker.isAutosensModeEnabled().value()) {
binding.infoLayout.sensitivityIcon.setImageResource(R.drawable.ic_swap_vert_black_48dp_green)
} else {
binding.infoLayout.sensitivityIcon.setImageResource(R.drawable.ic_x_swap_vert)
}
binding.infoLayout.sensitivity.text =
overviewData.lastAutosensData?.let { autosensData ->
String.format(Locale.ENGLISH, "%.0f%%", autosensData.autosensResult.ratio * 100)
} ?: ""
}
} }
} }
} }

View file

@ -34,18 +34,19 @@ class OverviewMenus @Inject constructor(
enum class CharType(@StringRes val nameId: Int, @ColorRes val colorId: Int, val primary: Boolean, val secondary: Boolean, @StringRes val shortnameId: Int) { enum class CharType(@StringRes val nameId: Int, @ColorRes val colorId: Int, val primary: Boolean, val secondary: Boolean, @StringRes val shortnameId: Int) {
PRE(R.string.overview_show_predictions, R.color.prediction, primary = true, secondary = false, shortnameId = R.string.prediction_shortname), PRE(R.string.overview_show_predictions, R.color.prediction, primary = true, secondary = false, shortnameId = R.string.prediction_shortname),
BAS(R.string.overview_show_basals, R.color.basal, primary = true, secondary = false,shortnameId = R.string.basal_shortname), BAS(R.string.overview_show_basals, R.color.basal, primary = true, secondary = false, shortnameId = R.string.basal_shortname),
ABS(R.string.overview_show_absinsulin, R.color.iob, primary = false, secondary = true,shortnameId = R.string.abs_insulin_shortname), ABS(R.string.overview_show_absinsulin, R.color.iob, primary = false, secondary = true, shortnameId = R.string.abs_insulin_shortname),
IOB(R.string.overview_show_iob, R.color.iob, primary = false, secondary = true,shortnameId = R.string.iob), IOB(R.string.overview_show_iob, R.color.iob, primary = false, secondary = true, shortnameId = R.string.iob),
COB(R.string.overview_show_cob, R.color.cob, primary = false, secondary = true,shortnameId = R.string.cob), COB(R.string.overview_show_cob, R.color.cob, primary = false, secondary = true, shortnameId = R.string.cob),
DEV(R.string.overview_show_deviations, R.color.bgi, primary = false, secondary = true,shortnameId = R.string.deviation_shortname), DEV(R.string.overview_show_deviations, R.color.bgi, primary = false, secondary = true, shortnameId = R.string.deviation_shortname),
BGI(R.string.overview_show_bgi, R.color.bgi, primary = false, secondary = true,shortnameId = R.string.bgi_shortname), BGI(R.string.overview_show_bgi, R.color.bgi, primary = false, secondary = true, shortnameId = R.string.bgi_shortname),
SEN(R.string.overview_show_sensitivity, R.color.ratio, primary = false, secondary = true,shortnameId = R.string.sensitivity_shortname), SEN(R.string.overview_show_sensitivity, R.color.ratio, primary = false, secondary = true, shortnameId = R.string.sensitivity_shortname),
ACT(R.string.overview_show_activity, R.color.activity, primary = true, secondary = false,shortnameId = R.string.activity_shortname), ACT(R.string.overview_show_activity, R.color.activity, primary = true, secondary = false, shortnameId = R.string.activity_shortname),
DEVSLOPE(R.string.overview_show_deviationslope, R.color.devslopepos, primary = false, secondary = true,shortnameId = R.string.devslope_shortname) DEVSLOPE(R.string.overview_show_deviationslope, R.color.devslopepos, primary = false, secondary = true, shortnameId = R.string.devslope_shortname)
} }
companion object { companion object {
const val MAX_GRAPHS = 5 // including main const val MAX_GRAPHS = 5 // including main
} }
@ -58,8 +59,6 @@ class OverviewMenus @Inject constructor(
return r.toString() return r.toString()
} }
private var _setting: MutableList<Array<Boolean>> = ArrayList() private var _setting: MutableList<Array<Boolean>> = ArrayList()
val setting: List<Array<Boolean>> val setting: List<Array<Boolean>>
@ -71,7 +70,7 @@ class OverviewMenus @Inject constructor(
aapsLogger.debug(sts) aapsLogger.debug(sts)
} }
private fun loadGraphConfig() { fun loadGraphConfig() {
val sts = sp.getString(R.string.key_graphconfig, "") val sts = sp.getString(R.string.key_graphconfig, "")
if (sts.isNotEmpty()) { if (sts.isNotEmpty()) {
_setting = Gson().fromJson(sts, Array<Array<Boolean>>::class.java).toMutableList() _setting = Gson().fromJson(sts, Array<Array<Boolean>>::class.java).toMutableList()
@ -88,7 +87,6 @@ class OverviewMenus @Inject constructor(
} }
fun setupChartMenu(chartButton: ImageButton) { fun setupChartMenu(chartButton: ImageButton) {
loadGraphConfig()
val settingsCopy = setting val settingsCopy = setting
val numOfGraphs = settingsCopy.size // 1 main + x secondary val numOfGraphs = settingsCopy.size // 1 main + x secondary
@ -100,6 +98,8 @@ class OverviewMenus @Inject constructor(
} }
val popup = PopupMenu(v.context, v) val popup = PopupMenu(v.context, v)
val used = arrayListOf<Int>()
for (g in 0 until numOfGraphs) { for (g in 0 until numOfGraphs) {
if (g != 0 && g < numOfGraphs) { if (g != 0 && g < numOfGraphs) {
val dividerItem = popup.menu.add(Menu.NONE, g, Menu.NONE, "------- ${resourceHelper.gs(R.string.graph_menu_divider_header)} $g -------") val dividerItem = popup.menu.add(Menu.NONE, g, Menu.NONE, "------- ${resourceHelper.gs(R.string.graph_menu_divider_header)} $g -------")
@ -112,6 +112,10 @@ class OverviewMenus @Inject constructor(
var insert = true var insert = true
if (m == CharType.PRE) insert = predictionsAvailable if (m == CharType.PRE) insert = predictionsAvailable
if (m == CharType.DEVSLOPE) insert = buildHelper.isDev() if (m == CharType.DEVSLOPE) insert = buildHelper.isDev()
if (used.contains(m.ordinal)) insert = false
for (g2 in g + 1 until numOfGraphs) {
if (settingsCopy[g2][m.ordinal]) insert = false
}
if (insert) { if (insert) {
val item = popup.menu.add(Menu.NONE, m.ordinal + 100 * (g + 1), Menu.NONE, resourceHelper.gs(m.nameId)) val item = popup.menu.add(Menu.NONE, m.ordinal + 100 * (g + 1), Menu.NONE, resourceHelper.gs(m.nameId))
val title = item.title val title = item.title
@ -120,6 +124,7 @@ class OverviewMenus @Inject constructor(
item.title = s item.title = s
item.isCheckable = true item.isCheckable = true
item.isChecked = settingsCopy[g][m.ordinal] item.isChecked = settingsCopy[g][m.ordinal]
if (settingsCopy[g][m.ordinal]) used.add(m.ordinal)
} }
} }
} }
@ -131,17 +136,21 @@ class OverviewMenus @Inject constructor(
popup.setOnMenuItemClickListener { popup.setOnMenuItemClickListener {
// id < 100 graph header - divider 1, 2, 3 ..... // id < 100 graph header - divider 1, 2, 3 .....
if (it.itemId == numOfGraphs) { when {
it.itemId == numOfGraphs -> {
// add new empty // add new empty
_setting.add(Array(CharType.values().size) { false }) _setting.add(Array(CharType.values().size) { false })
} else if (it.itemId < 100) { }
it.itemId < 100 -> {
// remove graph // remove graph
_setting.removeAt(it.itemId) _setting.removeAt(it.itemId)
} else { }
else -> {
val graphNumber = it.itemId / 100 - 1 val graphNumber = it.itemId / 100 - 1
val item = it.itemId % 100 val item = it.itemId % 100
_setting[graphNumber][item] = !it.isChecked _setting[graphNumber][item] = !it.isChecked
} }
}
storeGraphConfig() storeGraphConfig()
setupChartMenu(chartButton) setupChartMenu(chartButton)
rxBus.send(EventRefreshOverview("OnMenuItemClickListener", now = true)) rxBus.send(EventRefreshOverview("OnMenuItemClickListener", now = true))
@ -153,4 +162,11 @@ class OverviewMenus @Inject constructor(
} }
} }
fun isEnabledIn(type: CharType): Int {
val settingsCopy = setting
val numOfGraphs = settingsCopy.size // 1 main + x secondary
for (g in 0 until numOfGraphs) if (settingsCopy[g][type.ordinal]) return g
return -1
}
} }

View file

@ -3,20 +3,27 @@ package info.nightscout.androidaps.plugins.general.overview
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference import androidx.preference.SwitchPreference
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.EventRefreshOverview import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.events.*
import info.nightscout.androidaps.extensions.* import info.nightscout.androidaps.extensions.*
import info.nightscout.androidaps.interfaces.Overview import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.logging.AAPSLogger 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.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification 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.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverview
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.general.overview.notifications.NotificationStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventBucketedDataCreated
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.Translator
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.sharedPreferences.SP
@ -36,7 +43,15 @@ class OverviewPlugin @Inject constructor(
aapsLogger: AAPSLogger, aapsLogger: AAPSLogger,
private val aapsSchedulers: AapsSchedulers, private val aapsSchedulers: AapsSchedulers,
resourceHelper: ResourceHelper, resourceHelper: ResourceHelper,
private val config: Config private val config: Config,
private val dateUtil: DateUtil,
private val translator: Translator,
// private val profiler: Profiler,
private val profileFunction: ProfileFunction,
private val iobCobCalculator: IobCobCalculator,
private val repository: AppRepository,
private val overviewData: OverviewData,
private val overviewMenus: OverviewMenus
) : PluginBase(PluginDescription() ) : PluginBase(PluginDescription()
.mainType(PluginType.GENERAL) .mainType(PluginType.GENERAL)
.fragmentClass(OverviewFragment::class.qualifiedName) .fragmentClass(OverviewFragment::class.qualifiedName)
@ -52,8 +67,15 @@ class OverviewPlugin @Inject constructor(
private var disposable: CompositeDisposable = CompositeDisposable() private var disposable: CompositeDisposable = CompositeDisposable()
override val overviewBus = RxBusWrapper(aapsSchedulers)
class DeviationDataPoint(x: Double, y: Double, var color: Int, scale: Scale) : ScaledDataPoint(x, y, scale)
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
overviewMenus.loadGraphConfig()
overviewData.initRange()
notificationStore.createNotificationChannel() notificationStore.createNotificationChannel()
disposable += rxBus disposable += rxBus
.toObservable(EventNewNotification::class.java) .toObservable(EventNewNotification::class.java)
@ -69,6 +91,65 @@ class OverviewPlugin @Inject constructor(
if (notificationStore.remove(n.id)) if (notificationStore.remove(n.id))
rxBus.send(EventRefreshOverview("EventDismissNotification")) rxBus.send(EventRefreshOverview("EventDismissNotification"))
}, fabricPrivacy::logException) }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventIobCalculationProgress::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ overviewData.calcProgress = it.progress; overviewBus.send(EventUpdateOverview("EventIobCalculationProgress", OverviewData.Property.CALC_PROGRESS)) }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTempBasalChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ loadTemporaryBasal("EventTempBasalChange") }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventExtendedBolusChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ loadExtendedBolus("EventExtendedBolusChange") }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventNewBG::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ loadBg("EventNewBG") }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTempTargetChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ loadTemporaryTarget("EventTempTargetChange") }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTreatmentChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
loadIobCobResults("EventTreatmentChange")
overviewData.prepareTreatmentsData("EventTreatmentChange")
overviewBus.send(EventUpdateOverview("EventTreatmentChange", OverviewData.Property.GRAPH))
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTherapyEventChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
overviewData.prepareTreatmentsData("EventTherapyEventChange")
overviewBus.send(EventUpdateOverview("EventTherapyEventChange", OverviewData.Property.GRAPH))
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventBucketedDataCreated::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
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({ overviewData.preparePredictions("EventLoopInvoked") }, fabricPrivacy::logException)
disposable.add(rxBus
.toObservable(EventNewBasalProfile::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ loadProfile("EventNewBasalProfile") }, fabricPrivacy::logException))
disposable.add(rxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
if (it.cause !is EventCustomCalculationFinished) refreshLoop("EventAutosensCalculationFinished")
}, fabricPrivacy::logException))
Thread { loadAll("onResume") }.start()
} }
override fun onStop() { override fun onStop() {
@ -142,4 +223,93 @@ class OverviewPlugin @Inject constructor(
.storeDouble(R.string.key_statuslights_bat_warning, sp, resourceHelper) .storeDouble(R.string.key_statuslights_bat_warning, sp, resourceHelper)
.storeDouble(R.string.key_statuslights_bat_critical, sp, resourceHelper) .storeDouble(R.string.key_statuslights_bat_critical, sp, resourceHelper)
} }
@Volatile
var runningRefresh = false
override fun refreshLoop(from: String) {
if (runningRefresh) return
runningRefresh = true
loadIobCobResults(from)
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.BG))
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TIME))
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_BASAL))
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.EXTENDED_BOLUS))
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.IOB_COB))
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_TARGET))
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.SENSITIVITY))
loadAsData(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
}
@Suppress("SameParameterValue")
private fun loadAll(from: String) {
loadBg(from)
loadProfile(from)
loadTemporaryBasal(from)
loadExtendedBolus(from)
loadTemporaryTarget(from)
loadIobCobResults(from)
loadAsData(from)
overviewData.prepareBasalData(from)
overviewData.prepareTemporaryTargetData(from)
overviewData.prepareTreatmentsData(from)
// prepareIobAutosensData(from)
// preparePredictions(from)
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.GRAPH))
aapsLogger.debug(LTag.UI, "loadAll finished")
}
private fun loadProfile(from: String) {
overviewData.profile = profileFunction.getProfile()
overviewData.profileName = profileFunction.getProfileName()
overviewData.profileNameWithRemainingTime = profileFunction.getProfileNameWithRemainingTime()
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.PROFILE))
}
private fun loadTemporaryBasal(from: String) {
overviewData.temporaryBasal = iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now())
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_BASAL))
}
private fun loadExtendedBolus(from: String) {
overviewData.extendedBolus = iobCobCalculator.getExtendedBolus(dateUtil.now())
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.EXTENDED_BOLUS))
}
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
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_TARGET))
}
private fun loadAsData(from: String) {
overviewData.lastAutosensData = iobCobCalculator.ads.getLastAutosensData("Overview", aapsLogger, dateUtil)
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.SENSITIVITY))
}
private fun loadBg(from: String) {
val gvWrapped = repository.getLastGlucoseValueWrapped().blockingGet()
if (gvWrapped is ValueWrapper.Existing) overviewData.lastBg = gvWrapped.value
else overviewData.lastBg = null
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.BG))
}
private fun loadIobCobResults(from: String) {
overviewData.bolusIob = iobCobCalculator.calculateIobFromBolus().round()
overviewData.basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
overviewData.cobInfo = iobCobCalculator.getCobInfo(false, "Overview COB")
val lastCarbs = repository.getLastCarbsRecordWrapped().blockingGet()
overviewData.lastCarbsTime = if (lastCarbs is ValueWrapper.Existing) lastCarbs.value.timestamp else 0L
overviewBus.send(EventUpdateOverview(from, OverviewData.Property.IOB_COB))
}
} }

View file

@ -0,0 +1,6 @@
package info.nightscout.androidaps.plugins.general.overview.events
import info.nightscout.androidaps.events.Event
import info.nightscout.androidaps.plugins.general.overview.OverviewData
class EventUpdateOverview(val from: String, val what: OverviewData.Property) : Event()

View file

@ -4,52 +4,39 @@ import android.graphics.Color
import android.graphics.DashPathEffect import android.graphics.DashPathEffect
import android.graphics.Paint import android.graphics.Paint
import com.jjoe64.graphview.GraphView import com.jjoe64.graphview.GraphView
import com.jjoe64.graphview.series.BarGraphSeries
import com.jjoe64.graphview.series.DataPoint import com.jjoe64.graphview.series.DataPoint
import com.jjoe64.graphview.series.LineGraphSeries import com.jjoe64.graphview.series.LineGraphSeries
import com.jjoe64.graphview.series.Series import com.jjoe64.graphview.series.Series
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.IobTotal import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.extensions.target
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.general.overview.OverviewData
import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults import info.nightscout.androidaps.plugins.general.overview.graphExtensions.AreaGraphSeries
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.* import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DoubleDataPoint
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult import info.nightscout.androidaps.plugins.general.overview.graphExtensions.TimeAsXAxisLabelFormatter
import info.nightscout.androidaps.utils.* import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max import kotlin.math.max
import kotlin.math.min
class GraphData( class GraphData(
injector: HasAndroidInjector, injector: HasAndroidInjector,
private val graph: GraphView, private val graph: GraphView,
private val iobCobCalculator: IobCobCalculator private val overviewData: OverviewData
) { ) {
// IobCobCalculatorPlugin Cannot be injected: HistoryBrowser
@Inject lateinit var aapsLogger: AAPSLogger @Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var profileFunction: ProfileFunction @Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var databaseHelper: DatabaseHelperInterface
@Inject lateinit var repository: AppRepository
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var defaultValueHelper: DefaultValueHelper @Inject lateinit var defaultValueHelper: DefaultValueHelper
@Inject lateinit var translator: Translator
var maxY = Double.MIN_VALUE private var maxY = Double.MIN_VALUE
private var minY = Double.MAX_VALUE private var minY = Double.MAX_VALUE
private var bgReadingsArray: List<GlucoseValue>? = null
private val units: GlucoseUnit private val units: GlucoseUnit
private val series: MutableList<Series<*>> = ArrayList() private val series: MutableList<Series<*>> = ArrayList()
@ -58,587 +45,142 @@ class GraphData(
units = profileFunction.getUnits() units = profileFunction.getUnits()
} }
fun addBucketedData(fromTime: Long, toTime: Long) { fun addBucketedData() {
val bucketedData = iobCobCalculator.ads.getBucketedDataTableCopy() ?: return addSeries(overviewData.bucketedGraphSeries)
if (bucketedData.isEmpty()) {
aapsLogger.debug("No bucketed data.")
return
}
val bucketedListArray: MutableList<DataPointWithLabelInterface> = ArrayList()
for (inMemoryGlucoseValue in bucketedData) {
if (inMemoryGlucoseValue.timestamp < fromTime || inMemoryGlucoseValue.timestamp > toTime) continue
bucketedListArray.add(InMemoryGlucoseValueDataPoint(inMemoryGlucoseValue, profileFunction, resourceHelper))
}
addSeries(PointsWithLabelGraphSeries(Array(bucketedListArray.size) { i -> bucketedListArray[i] }))
} }
fun addBgReadings(fromTime: Long, toTime: Long, highLine: Double, predictions: MutableList<GlucoseValueDataPoint>?) { fun addBgReadings(addPredictions: Boolean) {
var maxBgValue = Double.MIN_VALUE maxY = if (overviewData.bgReadingsArray.isEmpty()) {
bgReadingsArray = repository.compatGetBgReadingsDataFromTime(fromTime, toTime, false).blockingGet() if (units == GlucoseUnit.MGDL) 180.0 else 10.0
if (bgReadingsArray?.isEmpty() != false) { } else overviewData.maxBgValue
aapsLogger.debug("No BG data.")
maxY = if (units == GlucoseUnit.MGDL) 180.0 else 10.0
minY = 0.0 minY = 0.0
return addSeries(overviewData.bgReadingGraphSeries)
if (addPredictions) addSeries(overviewData.predictionsGraphSeries)
} }
val bgListArray: MutableList<DataPointWithLabelInterface> = 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))
}
if (predictions != null) {
predictions.sortWith(Comparator { o1: GlucoseValueDataPoint, o2: GlucoseValueDataPoint -> o1.x.compareTo(o2.x) })
for (prediction in predictions) if (prediction.data.value >= 40) bgListArray.add(prediction)
}
maxBgValue = Profile.fromMgdlToUnits(maxBgValue, units)
maxBgValue = addUpperChartMargin(maxBgValue)
if (highLine > maxBgValue) maxBgValue = highLine
maxY = maxBgValue
minY = 0.0
addSeries(PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] }))
}
private fun addUpperChartMargin(maxBgValue: Double) =
if (units == GlucoseUnit.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4
fun addInRangeArea(fromTime: Long, toTime: Long, lowLine: Double, highLine: Double) { fun addInRangeArea(fromTime: Long, toTime: Long, lowLine: Double, highLine: Double) {
val inRangeAreaSeries: AreaGraphSeries<DoubleDataPoint>
val inRangeAreaDataPoints = arrayOf( val inRangeAreaDataPoints = arrayOf(
DoubleDataPoint(fromTime.toDouble(), lowLine, highLine), DoubleDataPoint(fromTime.toDouble(), lowLine, highLine),
DoubleDataPoint(toTime.toDouble(), lowLine, highLine) DoubleDataPoint(toTime.toDouble(), lowLine, highLine)
) )
inRangeAreaSeries = AreaGraphSeries(inRangeAreaDataPoints) addSeries(AreaGraphSeries(inRangeAreaDataPoints).also {
inRangeAreaSeries.color = 0 it.color = 0
inRangeAreaSeries.isDrawBackground = true
inRangeAreaSeries.backgroundColor = resourceHelper.gc(R.color.inrangebackground)
addSeries(inRangeAreaSeries)
}
// scale in % of vertical size (like 0.3)
fun addBasals(fromTime: Long, toTime: Long, scale: Double) {
var maxBasalValueFound = 0.0
val basalScale = Scale()
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 = 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
addSeries(LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also {
it.isDrawBackground = true it.isDrawBackground = true
it.backgroundColor = resourceHelper.gc(R.color.basebasal) it.backgroundColor = resourceHelper.gc(R.color.inrangebackground)
it.thickness = 0
})
addSeries(LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = resourceHelper.gc(R.color.tempbasal)
it.thickness = 0
})
addSeries(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)
})
})
addSeries(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)
})
})
basalScale.setMultiplier(maxY * scale / maxBasalValueFound)
}
fun addTargetLine(fromTime: Long, toTimeParam: Long, profile: Profile, lastRun: LoopInterface.LastRun?) {
var toTime = toTimeParam
val targetsSeriesArray: MutableList<DataPoint> = ArrayList()
var lastTarget = -1.0
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
addSeries(LineGraphSeries(Array(targetsSeriesArray.size) { i -> targetsSeriesArray[i] }).also {
it.isDrawBackground = false
it.color = resourceHelper.gc(R.color.tempTargetBackground)
it.thickness = 2
}) })
} }
fun addTreatments(fromTime: Long, endTime: Long) { fun addBasals() {
val filteredTreatments: MutableList<DataPointWithLabelInterface> = ArrayList() val scale = defaultValueHelper.determineLowLine() / maxY / 1.2
repository.getBolusesIncludingInvalidFromTimeToTime(fromTime, endTime, true).blockingGet() addSeries(overviewData.baseBasalGraphSeries)
.map { BolusDataPoint(it, resourceHelper, activePlugin, defaultValueHelper) } addSeries(overviewData.tempBasalGraphSeries)
.filter { it.data.type != Bolus.Type.SMB || it.data.isValid } addSeries(overviewData.basalLineGraphSeries)
.forEach { addSeries(overviewData.absoluteBasalGraphSeries)
it.y = getNearestBg(it.x.toLong()) overviewData.basalScale.multiplier = maxY * scale / overviewData.maxBasalValueFound
filteredTreatments.add(it)
}
repository.getCarbsIncludingInvalidFromTimeToTimeExpanded(fromTime, endTime, true).blockingGet()
.map { CarbsDataPoint(it, resourceHelper) }
.forEach {
it.y = getNearestBg(it.x.toLong())
filteredTreatments.add(it)
} }
// ProfileSwitch fun addTargetLine() {
repository.getEffectiveProfileSwitchDataFromTimeToTime(fromTime, endTime, true).blockingGet() addSeries(overviewData.temporaryTargetSeries)
.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 fun addTreatments() {
repository.compatGetTherapyEventDataFromToTime(fromTime - T.hours(6).msecs(), endTime).blockingGet() maxY = maxOf(maxY, overviewData.maxTreatmentsValue)
.map { TherapyEventDataPoint(it, resourceHelper, profileFunction, translator) } addSeries(overviewData.treatmentsSeries)
.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 fun addActivity(scale: Double) {
filteredTreatments.map { it.y } addSeries(overviewData.activitySeries)
.maxOrNull() addSeries(overviewData.activityPredictionSeries)
?.let(::addUpperChartMargin) overviewData.actScale.multiplier = maxY * scale / overviewData.maxIAValue
?.let { maxY = maxOf(maxY, it) }
addSeries(PointsWithLabelGraphSeries(filteredTreatments.toTypedArray()))
}
private fun getNearestBg(date: Long): Double {
bgReadingsArray?.let { bgReadingsArray ->
for (reading in bgReadingsArray) {
if (reading.timestamp > date) continue
return Profile.fromMgdlToUnits(reading.value, units)
}
return if (bgReadingsArray.isNotEmpty()) Profile.fromMgdlToUnits(bgReadingsArray[0].value, units) else Profile.fromMgdlToUnits(100.0, units)
} ?: return Profile.fromMgdlToUnits(100.0, units)
}
fun addActivity(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) {
val actArrayHist: MutableList<ScaledDataPoint> = ArrayList()
val actArrayPrediction: MutableList<ScaledDataPoint> = ArrayList()
val now = System.currentTimeMillis().toDouble()
val actScale = Scale()
var total: IobTotal
var maxIAValue = 0.0
var time = fromTime
while (time <= toTime) {
val profile = profileFunction.getProfile(time)
if (profile == null) {
time += 5 * 60 * 1000L
continue
}
total = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile)
val act: Double = total.activity
if (time <= now) actArrayHist.add(ScaledDataPoint(time, act, actScale)) else actArrayPrediction.add(ScaledDataPoint(time, act, actScale))
maxIAValue = max(maxIAValue, abs(act))
time += 5 * 60 * 1000L
}
addSeries(FixedLineGraphSeries(Array(actArrayHist.size) { i -> actArrayHist[i] }).also {
it.isDrawBackground = false
it.color = resourceHelper.gc(R.color.activity)
it.thickness = 3
})
addSeries(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)
})
})
if (useForScale) {
maxY = maxIAValue
minY = -maxIAValue
}
actScale.setMultiplier(maxY * scale / maxIAValue)
} }
//Function below show -BGI to be able to compare curves with deviations //Function below show -BGI to be able to compare curves with deviations
fun addMinusBGI(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, devBgiScale: Boolean) { fun addMinusBGI(useForScale: Boolean, scale: Double) {
val bgiArrayHist: MutableList<ScaledDataPoint> = ArrayList()
val bgiArrayPrediction: MutableList<ScaledDataPoint> = ArrayList()
val now = System.currentTimeMillis().toDouble()
val bgiScale = Scale()
var total: IobTotal
var maxBGIValue = 0.0
var time = fromTime
while (time <= toTime) {
val profile = profileFunction.getProfile(time)
if (profile == null) {
time += 5 * 60 * 1000L
continue
}
val deviation = if (devBgiScale) iobCobCalculator.ads.getAutosensDataAtTime(time)?.deviation
?: 0.0 else 0.0
total = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile)
val bgi: Double = total.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))
time += 5 * 60 * 1000L
}
addSeries(FixedLineGraphSeries(Array(bgiArrayHist.size) { i -> bgiArrayHist[i] }).also {
it.isDrawBackground = false
it.color = resourceHelper.gc(R.color.bgi)
it.thickness = 3
})
addSeries(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)
})
})
if (useForScale) { if (useForScale) {
maxY = maxBGIValue maxY = overviewData.maxBGIValue
minY = -maxBGIValue minY = -overviewData.maxBGIValue
} }
bgiScale.setMultiplier(maxY * scale / maxBGIValue) overviewData.bgiScale.multiplier = maxY * scale / overviewData.maxBGIValue
addSeries(overviewData.minusBgiSeries)
addSeries(overviewData.minusBgiHistSeries)
} }
// scale in % of vertical size (like 0.3) // scale in % of vertical size (like 0.3)
fun addIob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, showPrediction: Boolean, absScale: Boolean) { fun addIob(useForScale: Boolean, scale: Double) {
val iobSeries: FixedLineGraphSeries<ScaledDataPoint?>
val iobArray: MutableList<ScaledDataPoint> = ArrayList()
var maxIobValueFound = Double.MIN_VALUE
var lastIob = 0.0
val iobScale = Scale()
var time = fromTime
while (time <= toTime) {
val profile = profileFunction.getProfile(time)
if (profile == null) {
time += 5 * 60 * 1000L
continue
}
var iob = 0.0
var absIob = 0.0
iob = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile).iob
if (absScale) absIob = iobCobCalculator.calculateAbsInsulinFromTreatmentsAndTemps(time).iob
if (abs(lastIob - iob) > 0.02) {
if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale))
iobArray.add(ScaledDataPoint(time, iob, iobScale))
maxIobValueFound = if (absScale) max(maxIobValueFound, abs(absIob)) else max(maxIobValueFound, abs(iob))
lastIob = iob
}
time += 5 * 60 * 1000L
}
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
}
if (showPrediction) {
val autosensData = iobCobCalculator.getLastAutosensDataWithWaitForCalculationFinish("GraphData")
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)))
maxIobValueFound = max(maxIobValueFound, abs(i.iob))
}
addSeries(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)))
maxIobValueFound = max(maxIobValueFound, abs(i.iob))
}
addSeries(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))
}
if (useForScale) { if (useForScale) {
maxY = maxIobValueFound maxY = overviewData.maxIobValueFound
minY = -maxIobValueFound minY = -overviewData.maxIobValueFound
} }
iobScale.setMultiplier(maxY * scale / maxIobValueFound) overviewData.iobScale.multiplier = maxY * scale / overviewData.maxIobValueFound
addSeries(iobSeries) addSeries(overviewData.iobSeries)
addSeries(overviewData.iobPredictions1Series)
addSeries(overviewData.iobPredictions2Series)
} }
// scale in % of vertical size (like 0.3) // scale in % of vertical size (like 0.3)
fun addAbsIob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { fun addAbsIob(useForScale: Boolean, scale: Double) {
val iobSeries: FixedLineGraphSeries<ScaledDataPoint?>
val iobArray: MutableList<ScaledDataPoint> = ArrayList()
var maxIobValueFound = Double.MIN_VALUE
var lastIob = 0.0
val iobScale = Scale()
var time = fromTime
while (time <= toTime) {
val profile = profileFunction.getProfile(time)
if (profile == null) {
time += 5 * 60 * 1000L
continue
}
var iob = 0.0
iob = iobCobCalculator.calculateAbsInsulinFromTreatmentsAndTemps(time).iob
if (abs(lastIob - iob) > 0.02) {
if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale))
iobArray.add(ScaledDataPoint(time, iob, iobScale))
maxIobValueFound = max(maxIobValueFound, abs(iob))
lastIob = iob
}
time += 5 * 60 * 1000L
}
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
}
if (useForScale) { if (useForScale) {
maxY = maxIobValueFound maxY = overviewData.maxIobValueFound
minY = -maxIobValueFound minY = -overviewData.maxIobValueFound
} }
iobScale.setMultiplier(maxY * scale / maxIobValueFound) overviewData.iobScale.multiplier = maxY * scale / overviewData.maxIobValueFound
addSeries(iobSeries) addSeries(overviewData.absIobSeries)
} }
// scale in % of vertical size (like 0.3) // scale in % of vertical size (like 0.3)
fun addCob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { fun addCob(useForScale: Boolean, scale: Double) {
val minFailOverActiveList: MutableList<DataPointWithLabelInterface> = ArrayList()
val cobArray: MutableList<ScaledDataPoint> = ArrayList()
var maxCobValueFound = 0.0
var lastCob = 0
val cobScale = Scale()
var time = fromTime
while (time <= toTime) {
iobCobCalculator.ads.getAutosensDataAtTime(time)?.let { autosensData ->
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)
}
}
time += 5 * 60 * 1000L
}
// COB
addSeries(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
})
if (useForScale) { if (useForScale) {
maxY = maxCobValueFound maxY = overviewData.maxCobValueFound
minY = 0.0 minY = 0.0
} }
cobScale.setMultiplier(maxY * scale / maxCobValueFound) overviewData.cobScale.multiplier = maxY * scale / overviewData.maxCobValueFound
addSeries(PointsWithLabelGraphSeries(Array(minFailOverActiveList.size) { i -> minFailOverActiveList[i] })) addSeries(overviewData.cobSeries)
addSeries(overviewData.cobMinFailOverSeries)
} }
// scale in % of vertical size (like 0.3) // scale in % of vertical size (like 0.3)
fun addDeviations(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, devBgiScale: Boolean) { fun addDeviations(useForScale: Boolean, scale: Double) {
class DeviationDataPoint(x: Double, y: Double, var color: Int, scale: Scale) : ScaledDataPoint(x, y, scale)
val devArray: MutableList<DeviationDataPoint> = ArrayList()
var maxDevValueFound = 0.0
val devScale = Scale()
var time = fromTime
var total: IobTotal
while (time <= toTime) {
// if align Dev Scale with BGI scale, then calculate BGI value, else bgi = 0.0
val bgi: Double = if (devBgiScale) {
val profile = profileFunction.getProfile(time)
if (profile == null) {
time += 5 * 60 * 1000L
continue
}
total = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile)
total.activity * profile.getIsfMgdl(time) * 5.0
} else 0.0
iobCobCalculator.ads.getAutosensDataAtTime(time)?.let { autosensData ->
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, devScale))
maxDevValueFound = max(maxDevValueFound, max(abs(autosensData.deviation), abs(bgi)))
}
time += 5 * 60 * 1000L
}
// DEVIATIONS
addSeries(BarGraphSeries(Array(devArray.size) { i -> devArray[i] }).also {
it.setValueDependentColor { data: DeviationDataPoint -> data.color }
})
if (useForScale) { if (useForScale) {
maxY = maxDevValueFound maxY = overviewData.maxDevValueFound
minY = -maxY minY = -maxY
} }
devScale.setMultiplier(maxY * scale / maxDevValueFound) overviewData.devScale.multiplier = maxY * scale / overviewData.maxDevValueFound
addSeries(overviewData.deviationsSeries)
} }
// scale in % of vertical size (like 0.3) // scale in % of vertical size (like 0.3)
fun addRatio(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { fun addRatio(useForScale: Boolean, scale: Double) {
val ratioArray: MutableList<ScaledDataPoint> = ArrayList()
var maxRatioValueFound = 5.0 //even if sens data equals 0 for all the period, minimum scale is between 95% and 105%
var minRatioValueFound = -maxRatioValueFound
val ratioScale = if (useForScale) Scale(100.0) else Scale()
var time = fromTime
while (time <= toTime) {
iobCobCalculator.ads.getAutosensDataAtTime(time)?.let { autosensData ->
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))
}
time += 5 * 60 * 1000L
}
// RATIOS
addSeries(LineGraphSeries(Array(ratioArray.size) { i -> ratioArray[i] }).also {
it.color = resourceHelper.gc(R.color.ratio)
it.thickness = 3
})
if (useForScale) { if (useForScale) {
maxY = 100.0 + max(maxRatioValueFound, abs(minRatioValueFound)) maxY = 100.0 + max(overviewData.maxRatioValueFound, abs(overviewData.minRatioValueFound))
minY = 100.0 - max(maxRatioValueFound, abs(minRatioValueFound)) minY = 100.0 - max(overviewData.maxRatioValueFound, abs(overviewData.minRatioValueFound))
ratioScale.setMultiplier(1.0) overviewData.ratioScale.multiplier = 1.0
} else overviewData.ratioScale.shift = 100.0
ratioScale.setMultiplier(maxY * scale / max(maxRatioValueFound, abs(minRatioValueFound))) } else {
overviewData.ratioScale.multiplier = maxY * scale / max(overviewData.maxRatioValueFound, abs(overviewData.minRatioValueFound))
overviewData.ratioScale.shift = 0.0
}
addSeries(overviewData.ratioSeries)
} }
// scale in % of vertical size (like 0.3) // scale in % of vertical size (like 0.3)
fun addDeviationSlope(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { fun addDeviationSlope(useForScale: Boolean, scale: Double, isRatioScale: Boolean = false) {
val dsMaxArray: MutableList<ScaledDataPoint> = ArrayList()
val dsMinArray: MutableList<ScaledDataPoint> = ArrayList()
var maxFromMaxValueFound = 0.0
var maxFromMinValueFound = 0.0
val dsMaxScale = Scale()
val dsMinScale = Scale()
var time = fromTime
while (time <= toTime) {
iobCobCalculator.ads.getAutosensDataAtTime(time)?.let { autosensData ->
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
}
// Slopes
addSeries(LineGraphSeries(Array(dsMaxArray.size) { i -> dsMaxArray[i] }).also {
it.color = resourceHelper.gc(R.color.devslopepos)
it.thickness = 3
})
addSeries(LineGraphSeries(Array(dsMinArray.size) { i -> dsMinArray[i] }).also {
it.color = resourceHelper.gc(R.color.devslopeneg)
it.thickness = 3
})
if (useForScale) { if (useForScale) {
maxY = max(maxFromMaxValueFound, maxFromMinValueFound) maxY = max(overviewData.maxFromMaxValueFound, overviewData.maxFromMinValueFound)
minY = -maxY minY = -maxY
} }
dsMaxScale.setMultiplier(maxY * scale / maxFromMaxValueFound) var graphMaxY = maxY
dsMinScale.setMultiplier(maxY * scale / maxFromMinValueFound) if (isRatioScale) {
graphMaxY = maxY - 100.0
overviewData.dsMinScale.shift = 100.0
overviewData.dsMaxScale.shift = 100.0
} else {
overviewData.dsMinScale.shift = 0.0
overviewData.dsMaxScale.shift = 0.0
}
overviewData.dsMaxScale.multiplier = graphMaxY * scale / overviewData.maxFromMaxValueFound
overviewData.dsMinScale.multiplier = graphMaxY * scale / overviewData.maxFromMinValueFound
addSeries(overviewData.dsMaxSeries)
addSeries(overviewData.dsMinSeries)
} }
// scale in % of vertical size (like 0.3) // scale in % of vertical size (like 0.3)
@ -695,5 +237,3 @@ class GraphData(
} }
} }
private fun <E : DataPointWithLabelInterface> List<E>.filterTimeframe(fromTime: Long, endTime: Long): List<E> =
filter { it.x + it.duration >= fromTime && it.x <= endTime }

View file

@ -31,11 +31,11 @@ class TherapyEventDataPoint @Inject constructor(
if (data.glucose != null && data.glucose != 0.0) { if (data.glucose != null && data.glucose != 0.0) {
var mmol = 0.0 var mmol = 0.0
var mgdl = 0.0 var mgdl = 0.0
if (units == GlucoseUnit.MGDL) { if (data.glucoseUnit == TherapyEvent.GlucoseUnit.MGDL) {
mgdl = data.glucose!! mgdl = data.glucose!!
mmol = data.glucose!! * Constants.MGDL_TO_MMOLL mmol = data.glucose!! * Constants.MGDL_TO_MMOLL
} }
if (units == GlucoseUnit.MMOL) { if (data.glucoseUnit == TherapyEvent.GlucoseUnit.MMOL) {
mmol = data.glucose!! mmol = data.glucose!!
mgdl = data.glucose!! * Constants.MMOLL_TO_MGDL mgdl = data.glucose!! * Constants.MMOLL_TO_MGDL
} }

View file

@ -11,25 +11,26 @@ import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import androidx.work.workDataOf import androidx.work.workDataOf
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.Constants import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.DetailedBolusInfo import info.nightscout.androidaps.data.DetailedBolusInfo
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.entities.OfflineEvent
import info.nightscout.androidaps.database.entities.TemporaryTarget import info.nightscout.androidaps.database.entities.TemporaryTarget
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.transactions.CancelCurrentOfflineEventIfAnyTransaction
import info.nightscout.androidaps.database.transactions.CancelCurrentTemporaryTargetIfAnyTransaction import info.nightscout.androidaps.database.transactions.CancelCurrentTemporaryTargetIfAnyTransaction
import info.nightscout.androidaps.database.transactions.InsertTemporaryTargetAndCancelCurrentTransaction import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentOfflineEventTransaction
import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentTemporaryTargetTransaction
import info.nightscout.androidaps.events.EventPreferenceChange import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.events.EventRefreshOverview import info.nightscout.androidaps.events.EventRefreshOverview
import info.nightscout.androidaps.extensions.valueToUnitsString
import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
@ -41,7 +42,6 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProv
import info.nightscout.androidaps.queue.Callback import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.receivers.DataWorker import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.* import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.extensions.valueToUnitsString
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.sharedPreferences.SP
@ -72,7 +72,7 @@ class SmsCommunicatorPlugin @Inject constructor(
private val fabricPrivacy: FabricPrivacy, private val fabricPrivacy: FabricPrivacy,
private val activePlugin: ActivePlugin, private val activePlugin: ActivePlugin,
private val commandQueue: CommandQueueProvider, private val commandQueue: CommandQueueProvider,
private val loopPlugin: LoopPlugin, private val loop: Loop,
private val iobCobCalculator: IobCobCalculator, private val iobCobCalculator: IobCobCalculator,
private val xdripCalibrations: XdripCalibrations, private val xdripCalibrations: XdripCalibrations,
private var otp: OneTimePassword, private var otp: OneTimePassword,
@ -181,6 +181,7 @@ class SmsCommunicatorPlugin @Inject constructor(
} }
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
@kotlin.ExperimentalStdlibApi
override fun doWork(): Result { override fun doWork(): Result {
val bundle = dataWorker.pickupBundle(inputData.getLong(DataWorker.STORE_KEY, -1)) val bundle = dataWorker.pickupBundle(inputData.getLong(DataWorker.STORE_KEY, -1))
?: return Result.failure(workDataOf("Error" to "missing input data")) ?: return Result.failure(workDataOf("Error" to "missing input data"))
@ -223,6 +224,7 @@ class SmsCommunicatorPlugin @Inject constructor(
return false return false
} }
@kotlin.ExperimentalStdlibApi
fun processSms(receivedSms: Sms) { fun processSms(receivedSms: Sms) {
if (!isEnabled(PluginType.GENERAL)) { if (!isEnabled(PluginType.GENERAL)) {
aapsLogger.debug(LTag.SMS, "Ignoring SMS. Plugin disabled.") aapsLogger.debug(LTag.SMS, "Ignoring SMS. Plugin disabled.")
@ -246,8 +248,8 @@ class SmsCommunicatorPlugin @Inject constructor(
T.mins(sp.getLong(R.string.key_smscommunicator_remotebolusmindistance, T.msecs(Constants.remoteBolusMinDistance).mins())).msecs() T.mins(sp.getLong(R.string.key_smscommunicator_remotebolusmindistance, T.msecs(Constants.remoteBolusMinDistance).mins())).msecs()
else Constants.remoteBolusMinDistance else Constants.remoteBolusMinDistance
if (divided.isNotEmpty() && isCommand(divided[0].toUpperCase(Locale.getDefault()), receivedSms.phoneNumber)) { if (divided.isNotEmpty() && isCommand(divided[0].uppercase(Locale.getDefault()), receivedSms.phoneNumber)) {
when (divided[0].toUpperCase(Locale.getDefault())) { when (divided[0].uppercase(Locale.getDefault())) {
"BG" -> "BG" ->
if (divided.size == 1) processBG(receivedSms) if (divided.size == 1) processBG(receivedSms)
else sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.wrongformat))) else sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.wrongformat)))
@ -334,17 +336,18 @@ class SmsCommunicatorPlugin @Inject constructor(
receivedSms.processed = true receivedSms.processed = true
} }
@kotlin.ExperimentalStdlibApi
private fun processLOOP(divided: Array<String>, receivedSms: Sms) { private fun processLOOP(divided: Array<String>, receivedSms: Sms) {
when (divided[1].toUpperCase(Locale.getDefault())) { when (divided[1].uppercase(Locale.getDefault())) {
"DISABLE", "STOP" -> { "DISABLE", "STOP" -> {
if (loopPlugin.isEnabled(PluginType.LOOP)) { if (loop.enabled) {
val passCode = generatePassCode() val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_loopdisablereplywithcode), passCode) val reply = String.format(resourceHelper.gs(R.string.smscommunicator_loopdisablereplywithcode), passCode)
receivedSms.processed = true receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() { messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() {
override fun run() { override fun run() {
uel.log(Action.LOOP_DISABLED, Sources.SMS) uel.log(Action.LOOP_DISABLED, Sources.SMS)
loopPlugin.setPluginEnabled(PluginType.LOOP, false) loop.enabled = false
commandQueue.cancelTempBasal(true, object : Callback() { commandQueue.cancelTempBasal(true, object : Callback() {
override fun run() { override fun run() {
rxBus.send(EventRefreshOverview("SMS_LOOP_STOP")) rxBus.send(EventRefreshOverview("SMS_LOOP_STOP"))
@ -361,14 +364,14 @@ class SmsCommunicatorPlugin @Inject constructor(
} }
"ENABLE", "START" -> { "ENABLE", "START" -> {
if (!loopPlugin.isEnabled(PluginType.LOOP)) { if (!loop.enabled) {
val passCode = generatePassCode() val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_loopenablereplywithcode), passCode) val reply = String.format(resourceHelper.gs(R.string.smscommunicator_loopenablereplywithcode), passCode)
receivedSms.processed = true receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() { messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() {
override fun run() { override fun run() {
uel.log(Action.LOOP_ENABLED, Sources.SMS) uel.log(Action.LOOP_ENABLED, Sources.SMS)
loopPlugin.setPluginEnabled(PluginType.LOOP, true) loop.enabled= true
sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_loophasbeenenabled))) sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_loophasbeenenabled)))
rxBus.send(EventRefreshOverview("SMS_LOOP_START")) rxBus.send(EventRefreshOverview("SMS_LOOP_START"))
} }
@ -379,8 +382,8 @@ class SmsCommunicatorPlugin @Inject constructor(
} }
"STATUS" -> { "STATUS" -> {
val reply = if (loopPlugin.isEnabled(PluginType.LOOP)) { val reply = if (loop.enabled) {
if (loopPlugin.isSuspended) String.format(resourceHelper.gs(R.string.loopsuspendedfor), loopPlugin.minutesToEndOfSuspend()) if (loop.isSuspended) String.format(resourceHelper.gs(R.string.loopsuspendedfor), loop.minutesToEndOfSuspend())
else resourceHelper.gs(R.string.smscommunicator_loopisenabled) else resourceHelper.gs(R.string.smscommunicator_loopisenabled)
} else } else
resourceHelper.gs(R.string.loopisdisabled) resourceHelper.gs(R.string.loopisdisabled)
@ -395,7 +398,12 @@ class SmsCommunicatorPlugin @Inject constructor(
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() { messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() {
override fun run() { override fun run() {
uel.log(Action.RESUME, Sources.SMS) uel.log(Action.RESUME, Sources.SMS)
loopPlugin.suspendTo(0L) disposable += repository.runTransactionForResult(CancelCurrentOfflineEventIfAnyTransaction(dateUtil.now()))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
rxBus.send(EventRefreshOverview("SMS_LOOP_RESUME")) rxBus.send(EventRefreshOverview("SMS_LOOP_RESUME"))
commandQueue.cancelTempBasal(true, object : Callback() { commandQueue.cancelTempBasal(true, object : Callback() {
override fun run() { override fun run() {
@ -406,7 +414,6 @@ class SmsCommunicatorPlugin @Inject constructor(
} }
} }
}) })
loopPlugin.createOfflineEvent(0)
sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_loopresumed))) sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_loopresumed)))
} }
}) })
@ -431,8 +438,13 @@ class SmsCommunicatorPlugin @Inject constructor(
commandQueue.cancelTempBasal(true, object : Callback() { commandQueue.cancelTempBasal(true, object : Callback() {
override fun run() { override fun run() {
if (result.success) { if (result.success) {
loopPlugin.suspendTo(dateUtil.now() + anInteger() * 60L * 1000) disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(anInteger().toLong()).msecs(), OfflineEvent.Reason.SUSPEND))
loopPlugin.createOfflineEvent(anInteger() * 60) .subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
rxBus.send(EventRefreshOverview("SMS_LOOP_SUSPENDED")) rxBus.send(EventRefreshOverview("SMS_LOOP_SUSPENDED"))
val replyText = resourceHelper.gs(R.string.smscommunicator_loopsuspended) + " " + val replyText = resourceHelper.gs(R.string.smscommunicator_loopsuspended) + " " +
resourceHelper.gs(if (result.success) R.string.smscommunicator_tempbasalcanceled else R.string.smscommunicator_tempbasalcancelfailed) resourceHelper.gs(if (result.success) R.string.smscommunicator_tempbasalcanceled else R.string.smscommunicator_tempbasalcancelfailed)
@ -453,8 +465,9 @@ class SmsCommunicatorPlugin @Inject constructor(
} }
} }
@kotlin.ExperimentalStdlibApi
private fun processNSCLIENT(divided: Array<String>, receivedSms: Sms) { private fun processNSCLIENT(divided: Array<String>, receivedSms: Sms) {
if (divided[1].toUpperCase(Locale.getDefault()) == "RESTART") { if (divided[1].uppercase(Locale.getDefault()) == "RESTART") {
rxBus.send(EventNSClientRestart()) rxBus.send(EventNSClientRestart())
sendSMS(Sms(receivedSms.phoneNumber, "NSCLIENT RESTART SENT")) sendSMS(Sms(receivedSms.phoneNumber, "NSCLIENT RESTART SENT"))
receivedSms.processed = true receivedSms.processed = true
@ -462,6 +475,7 @@ class SmsCommunicatorPlugin @Inject constructor(
sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.wrongformat))) sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.wrongformat)))
} }
@kotlin.ExperimentalStdlibApi
private fun processHELP(divided: Array<String>, receivedSms: Sms) { private fun processHELP(divided: Array<String>, receivedSms: Sms) {
when { when {
divided.size == 1 -> { divided.size == 1 -> {
@ -469,8 +483,8 @@ class SmsCommunicatorPlugin @Inject constructor(
receivedSms.processed = true receivedSms.processed = true
} }
isCommand(divided[1].toUpperCase(Locale.getDefault()), receivedSms.phoneNumber) -> { isCommand(divided[1].uppercase(Locale.getDefault()), receivedSms.phoneNumber) -> {
commands[divided[1].toUpperCase(Locale.getDefault())]?.let { commands[divided[1].uppercase(Locale.getDefault())]?.let {
sendSMS(Sms(receivedSms.phoneNumber, it)) sendSMS(Sms(receivedSms.phoneNumber, it))
receivedSms.processed = true receivedSms.processed = true
} }
@ -507,10 +521,14 @@ class SmsCommunicatorPlugin @Inject constructor(
if (!result.success) { if (!result.success) {
sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_pumpconnectfail))) sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_pumpconnectfail)))
} else { } else {
loopPlugin.suspendTo(0L) disposable += repository.runTransactionForResult(CancelCurrentOfflineEventIfAnyTransaction(dateUtil.now()))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_reconnect))) sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_reconnect)))
rxBus.send(EventRefreshOverview("SMS_PUMP_START")) rxBus.send(EventRefreshOverview("SMS_PUMP_START"))
loopPlugin.createOfflineEvent(0)
} }
} }
}) })
@ -531,8 +549,8 @@ class SmsCommunicatorPlugin @Inject constructor(
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() { messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() {
override fun run() { override fun run() {
uel.log(Action.DISCONNECT, Sources.SMS) uel.log(Action.DISCONNECT, Sources.SMS)
val profile = profileFunction.getProfile() val profile = profileFunction.getProfile() ?: return
loopPlugin.disconnectPump(duration, profile) loop.goToZeroTemp(duration, profile, OfflineEvent.Reason.DISCONNECT_PUMP)
rxBus.send(EventRefreshOverview("SMS_PUMP_DISCONNECT")) rxBus.send(EventRefreshOverview("SMS_PUMP_DISCONNECT"))
sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_pumpdisconnected))) sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_pumpdisconnected)))
} }
@ -544,6 +562,7 @@ class SmsCommunicatorPlugin @Inject constructor(
} }
} }
@kotlin.ExperimentalStdlibApi
private fun processPROFILE(divided: Array<String>, receivedSms: Sms) { // load profiles private fun processPROFILE(divided: Array<String>, receivedSms: Sms) { // load profiles
val anInterface = activePlugin.activeProfileSource val anInterface = activePlugin.activeProfileSource
val store = anInterface.profile val store = anInterface.profile
@ -554,9 +573,9 @@ class SmsCommunicatorPlugin @Inject constructor(
} }
val profileName = profileFunction.getProfileName() val profileName = profileFunction.getProfileName()
val list = store.getProfileList() val list = store.getProfileList()
if (divided[1].toUpperCase(Locale.getDefault()) == "STATUS") { if (divided[1].uppercase(Locale.getDefault()) == "STATUS") {
sendSMS(Sms(receivedSms.phoneNumber, profileName)) sendSMS(Sms(receivedSms.phoneNumber, profileName))
} else if (divided[1].toUpperCase(Locale.getDefault()) == "LIST") { } else if (divided[1].uppercase(Locale.getDefault()) == "LIST") {
if (list.isEmpty()) sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.invalidprofile))) if (list.isEmpty()) sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.invalidprofile)))
else { else {
var reply = "" var reply = ""
@ -597,8 +616,9 @@ class SmsCommunicatorPlugin @Inject constructor(
receivedSms.processed = true receivedSms.processed = true
} }
@kotlin.ExperimentalStdlibApi
private fun processBASAL(divided: Array<String>, receivedSms: Sms) { private fun processBASAL(divided: Array<String>, receivedSms: Sms) {
if (divided[1].toUpperCase(Locale.getDefault()) == "CANCEL" || divided[1].toUpperCase(Locale.getDefault()) == "STOP") { if (divided[1].uppercase(Locale.getDefault()) == "CANCEL" || divided[1].uppercase(Locale.getDefault()) == "STOP") {
val passCode = generatePassCode() val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_basalstopreplywithcode), passCode) val reply = String.format(resourceHelper.gs(R.string.smscommunicator_basalstopreplywithcode), passCode)
receivedSms.processed = true receivedSms.processed = true
@ -713,8 +733,9 @@ class SmsCommunicatorPlugin @Inject constructor(
} }
} }
@kotlin.ExperimentalStdlibApi
private fun processEXTENDED(divided: Array<String>, receivedSms: Sms) { private fun processEXTENDED(divided: Array<String>, receivedSms: Sms) {
if (divided[1].toUpperCase(Locale.getDefault()) == "CANCEL" || divided[1].toUpperCase(Locale.getDefault()) == "STOP") { if (divided[1].uppercase(Locale.getDefault()) == "CANCEL" || divided[1].uppercase(Locale.getDefault()) == "STOP") {
val passCode = generatePassCode() val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_extendedstopreplywithcode), passCode) val reply = String.format(resourceHelper.gs(R.string.smscommunicator_extendedstopreplywithcode), passCode)
receivedSms.processed = true receivedSms.processed = true
@ -824,7 +845,7 @@ class SmsCommunicatorPlugin @Inject constructor(
currentProfile.units == GlucoseUnit.MMOL -> Constants.defaultEatingSoonTTmmol currentProfile.units == GlucoseUnit.MMOL -> Constants.defaultEatingSoonTTmmol
else -> Constants.defaultEatingSoonTTmgdl else -> Constants.defaultEatingSoonTTmgdl
} }
disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction( disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = dateUtil.now(), timestamp = dateUtil.now(),
duration = TimeUnit.MINUTES.toMillis(eatingSoonTTDuration.toLong()), duration = TimeUnit.MINUTES.toMillis(eatingSoonTTDuration.toLong()),
reason = TemporaryTarget.Reason.EATING_SOON, reason = TemporaryTarget.Reason.EATING_SOON,
@ -879,11 +900,12 @@ class SmsCommunicatorPlugin @Inject constructor(
return retVal return retVal
} }
@kotlin.ExperimentalStdlibApi
private fun processCARBS(divided: Array<String>, receivedSms: Sms) { private fun processCARBS(divided: Array<String>, receivedSms: Sms) {
var grams = SafeParse.stringToInt(divided[1]) var grams = SafeParse.stringToInt(divided[1])
var time = dateUtil.now() var time = dateUtil.now()
if (divided.size > 2) { if (divided.size > 2) {
time = toTodayTime(divided[2].toUpperCase(Locale.getDefault())) time = toTodayTime(divided[2].uppercase(Locale.getDefault()))
if (time == 0L) { if (time == 0L) {
sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.wrongformat))) sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.wrongformat)))
return return
@ -922,6 +944,7 @@ class SmsCommunicatorPlugin @Inject constructor(
} }
} }
@kotlin.ExperimentalStdlibApi
private fun processTARGET(divided: Array<String>, receivedSms: Sms) { private fun processTARGET(divided: Array<String>, receivedSms: Sms) {
val isMeal = divided[1].equals("MEAL", ignoreCase = true) val isMeal = divided[1].equals("MEAL", ignoreCase = true)
val isActivity = divided[1].equals("ACTIVITY", ignoreCase = true) val isActivity = divided[1].equals("ACTIVITY", ignoreCase = true)
@ -929,7 +952,7 @@ class SmsCommunicatorPlugin @Inject constructor(
val isStop = divided[1].equals("STOP", ignoreCase = true) || divided[1].equals("CANCEL", ignoreCase = true) val isStop = divided[1].equals("STOP", ignoreCase = true) || divided[1].equals("CANCEL", ignoreCase = true)
if (isMeal || isActivity || isHypo) { if (isMeal || isActivity || isHypo) {
val passCode = generatePassCode() val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_temptargetwithcode), divided[1].toUpperCase(Locale.getDefault()), passCode) val reply = String.format(resourceHelper.gs(R.string.smscommunicator_temptargetwithcode), divided[1].uppercase(Locale.getDefault()), passCode)
receivedSms.processed = true receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() { messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() {
override fun run() { override fun run() {
@ -969,7 +992,7 @@ class SmsCommunicatorPlugin @Inject constructor(
var tt = sp.getDouble(keyTarget, if (units == GlucoseUnit.MMOL) defaultTargetMMOL else defaultTargetMGDL) var tt = sp.getDouble(keyTarget, if (units == GlucoseUnit.MMOL) defaultTargetMMOL else defaultTargetMGDL)
tt = Profile.toCurrentUnits(profileFunction, tt) tt = Profile.toCurrentUnits(profileFunction, tt)
tt = if (tt > 0) tt else if (units == GlucoseUnit.MMOL) defaultTargetMMOL else defaultTargetMGDL tt = if (tt > 0) tt else if (units == GlucoseUnit.MMOL) defaultTargetMMOL else defaultTargetMGDL
disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction( disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = dateUtil.now(), timestamp = dateUtil.now(),
duration = TimeUnit.MINUTES.toMillis(ttDuration.toLong()), duration = TimeUnit.MINUTES.toMillis(ttDuration.toLong()),
reason = TemporaryTarget.Reason.EATING_SOON, reason = TemporaryTarget.Reason.EATING_SOON,

View file

@ -20,7 +20,7 @@ import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.interfaces.end import info.nightscout.androidaps.database.interfaces.end
import info.nightscout.androidaps.database.transactions.CancelCurrentTemporaryTargetIfAnyTransaction import info.nightscout.androidaps.database.transactions.CancelCurrentTemporaryTargetIfAnyTransaction
import info.nightscout.androidaps.database.transactions.InsertTemporaryTargetAndCancelCurrentTransaction import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentTemporaryTargetTransaction
import info.nightscout.androidaps.extensions.total import info.nightscout.androidaps.extensions.total
import info.nightscout.androidaps.extensions.valueToUnits import info.nightscout.androidaps.extensions.valueToUnits
import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.interfaces.*
@ -572,7 +572,7 @@ class ActionStringHandler @Inject constructor(
private fun generateTempTarget(duration: Int, low: Double, high: Double) { private fun generateTempTarget(duration: Int, low: Double, high: Double) {
if (duration != 0) { if (duration != 0) {
disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction( disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = System.currentTimeMillis(), timestamp = System.currentTimeMillis(),
duration = TimeUnit.MINUTES.toMillis(duration.toLong()), duration = TimeUnit.MINUTES.toMillis(duration.toLong()),
reason = TemporaryTarget.Reason.WEAR, reason = TemporaryTarget.Reason.WEAR,

View file

@ -7,7 +7,6 @@ import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.IobTotal import info.nightscout.androidaps.data.IobTotal
import info.nightscout.androidaps.data.MealData import info.nightscout.androidaps.data.MealData
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.Bolus import info.nightscout.androidaps.database.entities.Bolus
@ -71,7 +70,6 @@ open class IobCobCalculatorPlugin @Inject constructor(
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
private var iobTable = LongSparseArray<IobTotal>() // oldest at index 0 private var iobTable = LongSparseArray<IobTotal>() // oldest at index 0
private var absIobTable = LongSparseArray<IobTotal>() // oldest at index 0, absolute insulin in the body
private var basalDataTable = LongSparseArray<BasalData>() // oldest at index 0 private var basalDataTable = LongSparseArray<BasalData>() // oldest at index 0
override var ads: AutosensDataStore = AutosensDataStore() override var ads: AutosensDataStore = AutosensDataStore()
@ -169,10 +167,9 @@ open class IobCobCalculatorPlugin @Inject constructor(
return getBGDataFrom return getBGDataFrom
} }
override fun calculateFromTreatmentsAndTemps(fromTime: Long, profile: Profile): IobTotal { override fun calculateFromTreatmentsAndTemps(toTime: Long, profile: Profile): IobTotal {
synchronized(dataLock) {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
val time = ads.roundUpTime(fromTime) val time = ads.roundUpTime(toTime)
val cacheHit = iobTable[time] val cacheHit = iobTable[time]
if (time < now && cacheHit != null) { if (time < now && cacheHit != null) {
//og.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString()); //og.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString());
@ -196,30 +193,12 @@ open class IobCobCalculatorPlugin @Inject constructor(
basalIob.iobWithZeroTemp = IobTotal.combine(bolusIob, basalIobWithZeroTemp).round() basalIob.iobWithZeroTemp = IobTotal.combine(bolusIob, basalIobWithZeroTemp).round()
val iobTotal = IobTotal.combine(bolusIob, basalIob).round() val iobTotal = IobTotal.combine(bolusIob, basalIob).round()
if (time < System.currentTimeMillis()) { if (time < System.currentTimeMillis()) {
synchronized(dataLock) {
iobTable.put(time, iobTotal) iobTable.put(time, iobTotal)
} }
return iobTotal
}
}
override fun calculateAbsInsulinFromTreatmentsAndTemps(fromTime: Long): IobTotal {
synchronized(dataLock) {
val now = System.currentTimeMillis()
val time = ads.roundUpTime(fromTime)
val cacheHit = absIobTable[time]
if (time < now && cacheHit != null) {
//log.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString());
return cacheHit
} // else log.debug(">>> calculateFromTreatmentsAndTemps Cache miss " + new Date(time).toLocaleString());
val bolusIob = calculateIobFromBolusToTime(time).round()
val basalIob = calculateAbsoluteIobTempBasals(time).round()
val iobTotal = IobTotal.combine(bolusIob, basalIob).round()
if (time < System.currentTimeMillis()) {
absIobTable.put(time, iobTotal)
} }
return iobTotal return iobTotal
} }
}
private fun calculateFromTreatmentsAndTemps(time: Long, lastAutosensResult: AutosensResult, exercise_mode: Boolean, half_basal_exercise_target: Int, isTempTarget: Boolean): IobTotal { private fun calculateFromTreatmentsAndTemps(time: Long, lastAutosensResult: AutosensResult, exercise_mode: Boolean, half_basal_exercise_target: Int, isTempTarget: Boolean): IobTotal {
val now = dateUtil.now() val now = dateUtil.now()
@ -246,7 +225,6 @@ open class IobCobCalculatorPlugin @Inject constructor(
} }
override fun getBasalData(profile: Profile, fromTime: Long): BasalData { override fun getBasalData(profile: Profile, fromTime: Long): BasalData {
synchronized(dataLock) {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
val time = ads.roundUpTime(fromTime) val time = ads.roundUpTime(fromTime)
var retVal = basalDataTable[time] var retVal = basalDataTable[time]
@ -263,12 +241,13 @@ open class IobCobCalculatorPlugin @Inject constructor(
retVal.tempBasalAbsolute = retVal.basal retVal.tempBasalAbsolute = retVal.basal
} }
if (time < now) { if (time < now) {
synchronized(dataLock) {
basalDataTable.append(time, retVal) basalDataTable.append(time, retVal)
} }
}
} //else log.debug(">>> getBasalData Cache hit " + new Date(time).toLocaleString()); } //else log.debug(">>> getBasalData Cache hit " + new Date(time).toLocaleString());
return retVal return retVal
} }
}
override fun getLastAutosensDataWithWaitForCalculationFinish(reason: String): AutosensData? { override fun getLastAutosensDataWithWaitForCalculationFinish(reason: String): AutosensData? {
if (thread?.isAlive == true) { if (thread?.isAlive == true) {
@ -408,14 +387,6 @@ open class IobCobCalculatorPlugin @Inject constructor(
break break
} }
} }
for (index in absIobTable.size() - 1 downTo 0) {
if (absIobTable.keyAt(index) > time) {
aapsLogger.debug(LTag.AUTOSENS, "Removing from absIobTable: " + dateUtil.dateAndTimeAndSecondsString(absIobTable.keyAt(index)))
absIobTable.removeAt(index)
} else {
break
}
}
for (index in basalDataTable.size() - 1 downTo 0) { for (index in basalDataTable.size() - 1 downTo 0) {
if (basalDataTable.keyAt(index) > time) { if (basalDataTable.keyAt(index) > time) {
aapsLogger.debug(LTag.AUTOSENS, "Removing from basalDataTable: " + dateUtil.dateAndTimeAndSecondsString(basalDataTable.keyAt(index))) aapsLogger.debug(LTag.AUTOSENS, "Removing from basalDataTable: " + dateUtil.dateAndTimeAndSecondsString(basalDataTable.keyAt(index)))
@ -459,8 +430,8 @@ open class IobCobCalculatorPlugin @Inject constructor(
* Time range to the past for IOB calculation * Time range to the past for IOB calculation
* @return milliseconds * @return milliseconds
*/ */
fun range(): Long = ((profileFunction.getProfile()?.dia fun range(): Long = ((/*overviewData.rangeToDisplay + */(profileFunction.getProfile()?.dia
?: Constants.defaultDIA) * 60 * 60 * 1000).toLong() ?: Constants.defaultDIA)) * 60 * 60 * 1000).toLong()
override fun calculateIobFromBolus(): IobTotal = calculateIobFromBolusToTime(dateUtil.now()) override fun calculateIobFromBolus(): IobTotal = calculateIobFromBolusToTime(dateUtil.now())
@ -533,6 +504,7 @@ open class IobCobCalculatorPlugin @Inject constructor(
} }
override fun getTempBasalIncludingConvertedExtended(timestamp: Long): TemporaryBasal? { override fun getTempBasalIncludingConvertedExtended(timestamp: Long): TemporaryBasal? {
val tb = repository.getTemporaryBasalActiveAt(timestamp).blockingGet() val tb = repository.getTemporaryBasalActiveAt(timestamp).blockingGet()
if (tb is ValueWrapper.Existing) return tb.value if (tb is ValueWrapper.Existing) return tb.value
val eb = repository.getExtendedBolusActiveAt(timestamp).blockingGet() val eb = repository.getExtendedBolusActiveAt(timestamp).blockingGet()
@ -542,7 +514,7 @@ open class IobCobCalculatorPlugin @Inject constructor(
return null return null
} }
override fun calculateAbsoluteIobTempBasals(toTime: Long): IobTotal { override fun calculateAbsoluteIobFromBaseBasals(toTime: Long): IobTotal {
val total = IobTotal(toTime) val total = IobTotal(toTime)
var i = toTime - range() var i = toTime - range()
while (i < toTime) { while (i < toTime) {
@ -551,8 +523,7 @@ open class IobCobCalculatorPlugin @Inject constructor(
i += T.mins(5).msecs() i += T.mins(5).msecs()
continue continue
} }
val runningTBR = getTempBasalIncludingConvertedExtended(i) val running = profile.getBasal(i)
val running = runningTBR?.convertedToAbsolute(i, profile) ?: profile.getBasal(i)
val bolus = Bolus( val bolus = Bolus(
timestamp = i, timestamp = i,
amount = running * 5.0 / 60.0, amount = running * 5.0 / 60.0,

View file

@ -82,7 +82,7 @@ class IobCobOref1Thread internal constructor(
//log.debug("Locking calculateSensitivityData"); //log.debug("Locking calculateSensitivityData");
val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable) val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable)
if (bgDataReload) { if (bgDataReload) {
iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil) iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil, rxBus)
iobCobCalculatorPlugin.clearCache() iobCobCalculatorPlugin.clearCache()
} }
// work on local copy and set back when finished // work on local copy and set back when finished
@ -99,7 +99,7 @@ class IobCobOref1Thread internal constructor(
// start from oldest to be able sub cob // start from oldest to be able sub cob
for (i in bucketedData.size - 4 downTo 0) { for (i in bucketedData.size - 4 downTo 0) {
val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else "" val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else ""
rxBus.send(EventIobCalculationProgress(progress)) rxBus.send(EventIobCalculationProgress(progress, cause))
if (iobCobCalculatorPlugin.stopCalculationTrigger) { if (iobCobCalculatorPlugin.stopCalculationTrigger) {
iobCobCalculatorPlugin.stopCalculationTrigger = false iobCobCalculatorPlugin.stopCalculationTrigger = false
aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from") aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from")
@ -325,7 +325,7 @@ class IobCobOref1Thread internal constructor(
}.start() }.start()
} finally { } finally {
mWakeLock?.release() mWakeLock?.release()
rxBus.send(EventIobCalculationProgress("")) rxBus.send(EventIobCalculationProgress("", cause))
aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from") aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from")
profiler.log(LTag.AUTOSENS, "IobCobOref1Thread", start) profiler.log(LTag.AUTOSENS, "IobCobOref1Thread", start)
} }

View file

@ -81,7 +81,7 @@ class IobCobThread @Inject internal constructor(
//log.debug("Locking calculateSensitivityData"); //log.debug("Locking calculateSensitivityData");
val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable) val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable)
if (bgDataReload) { if (bgDataReload) {
iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil) iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil, rxBus)
iobCobCalculatorPlugin.clearCache() iobCobCalculatorPlugin.clearCache()
} }
// work on local copy and set back when finished // work on local copy and set back when finished
@ -98,7 +98,7 @@ class IobCobThread @Inject internal constructor(
// start from oldest to be able sub cob // start from oldest to be able sub cob
for (i in bucketedData.size - 4 downTo 0) { for (i in bucketedData.size - 4 downTo 0) {
val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else "" val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else ""
rxBus.send(EventIobCalculationProgress(progress)) rxBus.send(EventIobCalculationProgress(progress, cause))
if (iobCobCalculatorPlugin.stopCalculationTrigger) { if (iobCobCalculatorPlugin.stopCalculationTrigger) {
iobCobCalculatorPlugin.stopCalculationTrigger = false iobCobCalculatorPlugin.stopCalculationTrigger = false
aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from") aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from")
@ -272,7 +272,7 @@ class IobCobThread @Inject internal constructor(
}.start() }.start()
} finally { } finally {
mWakeLock?.release() mWakeLock?.release()
rxBus.send(EventIobCalculationProgress("")) rxBus.send(EventIobCalculationProgress("", cause))
aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from") aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from")
profiler.log(LTag.AUTOSENS, "IobCobThread", start) 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 import info.nightscout.androidaps.events.Event
class EventIobCalculationProgress(var progress: String) : Event() class EventIobCalculationProgress(val progress: String, val cause: Event?) : Event()

View file

@ -54,6 +54,9 @@ class LocalProfileFragment : DaggerFragment() {
private val save = Runnable { private val save = Runnable {
doEdit() doEdit()
basalView?.updateLabel(resourceHelper.gs(R.string.basal_label) + ": " + sumLabel()) basalView?.updateLabel(resourceHelper.gs(R.string.basal_label) + ": " + sumLabel())
localProfilePlugin.profile?.getSpecificProfile(spinner?.selectedItem.toString())?.let {
binding.basalGraph.show(ProfileSealed.Pure(it))
}
} }
private val textWatch = object : TextWatcher { private val textWatch = object : TextWatcher {
@ -67,7 +70,7 @@ class LocalProfileFragment : DaggerFragment() {
} }
private fun sumLabel(): String { private fun sumLabel(): String {
val profile = localProfilePlugin.createProfileStore().getDefaultProfile() val profile = localProfilePlugin.profile?.getDefaultProfile()
val sum = profile?.let { ProfileSealed.Pure(profile).baseBasalSum() } ?: 0.0 val sum = profile?.let { ProfileSealed.Pure(profile).baseBasalSum() } ?: 0.0
return "" + DecimalFormatter.to2Decimal(sum) + resourceHelper.gs(R.string.insulin_unit_shortname) return "" + DecimalFormatter.to2Decimal(sum) + resourceHelper.gs(R.string.insulin_unit_shortname)
} }
@ -124,7 +127,7 @@ class LocalProfileFragment : DaggerFragment() {
binding.dia.setParams(currentProfile.dia, hardLimits.minDia(), hardLimits.maxDia(), 0.1, DecimalFormat("0.0"), false, binding.save, textWatch) binding.dia.setParams(currentProfile.dia, hardLimits.minDia(), hardLimits.maxDia(), 0.1, DecimalFormat("0.0"), false, binding.save, textWatch)
binding.dia.tag = "LP_DIA" binding.dia.tag = "LP_DIA"
TimeListEdit(context, aapsLogger, dateUtil, view, R.id.ic, "IC", resourceHelper.gs(R.string.ic_label), currentProfile.ic, null, hardLimits.minIC(), hardLimits.maxIC(), 0.1, DecimalFormat("0.0"), save) TimeListEdit(context, aapsLogger, dateUtil, view, R.id.ic, "IC", resourceHelper.gs(R.string.ic_label), currentProfile.ic, null, hardLimits.minIC(), hardLimits.maxIC(), 0.1, DecimalFormat("0.0"), save)
basalView = TimeListEdit(context, aapsLogger, dateUtil, view, R.id.basal, "BASAL", resourceHelper.gs(R.string.basal_label) + ": " + sumLabel(), currentProfile.basal, null, pumpDescription.basalMinimumRate, 10.0, 0.01, DecimalFormat("0.00"), save) basalView = TimeListEdit(context, aapsLogger, dateUtil, view, R.id.basal_holder, "BASAL", resourceHelper.gs(R.string.basal_label) + ": " + sumLabel(), currentProfile.basal, null, pumpDescription.basalMinimumRate, 10.0, 0.01, DecimalFormat("0.00"), save)
if (units == Constants.MGDL) { if (units == Constants.MGDL) {
TimeListEdit(context, aapsLogger, dateUtil, view, R.id.isf, "ISF", resourceHelper.gs(R.string.isf_label), currentProfile.isf, null, HardLimits.MIN_ISF, HardLimits.MAX_ISF, 1.0, DecimalFormat("0"), save) TimeListEdit(context, aapsLogger, dateUtil, view, R.id.isf, "ISF", resourceHelper.gs(R.string.isf_label), currentProfile.isf, null, HardLimits.MIN_ISF, HardLimits.MAX_ISF, 1.0, DecimalFormat("0"), save)
TimeListEdit(context, aapsLogger, dateUtil, view, R.id.target, "TARGET", resourceHelper.gs(R.string.target_label), currentProfile.targetLow, currentProfile.targetHigh, HardLimits.VERY_HARD_LIMIT_TARGET_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TARGET_BG[1].toDouble(), 1.0, DecimalFormat("0"), save) TimeListEdit(context, aapsLogger, dateUtil, view, R.id.target, "TARGET", resourceHelper.gs(R.string.target_label), currentProfile.targetLow, currentProfile.targetHigh, HardLimits.VERY_HARD_LIMIT_TARGET_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TARGET_BG[1].toDouble(), 1.0, DecimalFormat("0"), save)
@ -162,6 +165,9 @@ class LocalProfileFragment : DaggerFragment() {
} }
} }
}) })
localProfilePlugin.profile?.getSpecificProfile(spinner?.selectedItem.toString())?.let {
binding.basalGraph.show(ProfileSealed.Pure(it))
}
binding.profileAdd.setOnClickListener { binding.profileAdd.setOnClickListener {
if (localProfilePlugin.isEdited) { if (localProfilePlugin.isEdited) {

View file

@ -1,6 +1,10 @@
package info.nightscout.androidaps.plugins.profile.local package info.nightscout.androidaps.plugins.profile.local
import android.content.Context
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.Constants import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
@ -12,7 +16,8 @@ import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged
import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DecimalFormatter import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.HardLimits import info.nightscout.androidaps.utils.HardLimits
@ -22,6 +27,7 @@ import info.nightscout.androidaps.utils.sharedPreferences.SP
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import java.lang.Integer.min
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -35,7 +41,6 @@ class LocalProfilePlugin @Inject constructor(
resourceHelper: ResourceHelper, resourceHelper: ResourceHelper,
private val sp: SP, private val sp: SP,
private val profileFunction: ProfileFunction, private val profileFunction: ProfileFunction,
private val nsUpload: NSUpload,
private val activePlugin: ActivePlugin, private val activePlugin: ActivePlugin,
private val hardLimits: HardLimits, private val hardLimits: HardLimits,
private val dateUtil: DateUtil private val dateUtil: DateUtil
@ -134,6 +139,7 @@ class LocalProfilePlugin @Inject constructor(
} }
sp.putInt(Constants.LOCAL_PROFILE + "_profiles", numOfProfiles) sp.putInt(Constants.LOCAL_PROFILE + "_profiles", numOfProfiles)
sp.putLong(R.string.key_local_profile_last_change, dateUtil.now())
createAndStoreConvertedProfile() createAndStoreConvertedProfile()
isEdited = false isEdited = false
aapsLogger.debug(LTag.PROFILE, "Storing settings: " + rawProfile?.data.toString()) aapsLogger.debug(LTag.PROFILE, "Storing settings: " + rawProfile?.data.toString())
@ -143,10 +149,7 @@ class LocalProfilePlugin @Inject constructor(
val name = it.name ?: "." val name = it.name ?: "."
if (name.contains(".")) namesOK = false if (name.contains(".")) namesOK = false
} }
if (namesOK) if (!namesOK) activity?.let {
rawProfile?.let { nsUpload.uploadProfileStore(it.data) }
else
activity?.let {
OKDialog.show(it, "", resourceHelper.gs(R.string.profilenamecontainsdot)) OKDialog.show(it, "", resourceHelper.gs(R.string.profilenamecontainsdot))
} }
} }
@ -167,61 +170,64 @@ class LocalProfilePlugin @Inject constructor(
p.dia = sp.getDouble(localProfileNumbered + "dia", Constants.defaultDIA) p.dia = sp.getDouble(localProfileNumbered + "dia", Constants.defaultDIA)
try { try {
p.ic = JSONArray(sp.getString(localProfileNumbered + "ic", defaultArray)) p.ic = JSONArray(sp.getString(localProfileNumbered + "ic", defaultArray))
} catch (e1: JSONException) {
try {
p.ic = JSONArray(defaultArray)
} catch (ignored: JSONException) {
}
aapsLogger.error("Exception", e1)
}
try {
p.isf = JSONArray(sp.getString(localProfileNumbered + "isf", defaultArray)) p.isf = JSONArray(sp.getString(localProfileNumbered + "isf", defaultArray))
} catch (e1: JSONException) {
try {
p.isf = JSONArray(defaultArray)
} catch (ignored: JSONException) {
}
aapsLogger.error("Exception", e1)
}
try {
p.basal = JSONArray(sp.getString(localProfileNumbered + "basal", defaultArray)) p.basal = JSONArray(sp.getString(localProfileNumbered + "basal", defaultArray))
} catch (e1: JSONException) {
try {
p.basal = JSONArray(defaultArray)
} catch (ignored: JSONException) {
}
aapsLogger.error("Exception", e1)
}
try {
p.targetLow = JSONArray(sp.getString(localProfileNumbered + "targetlow", defaultArray)) p.targetLow = JSONArray(sp.getString(localProfileNumbered + "targetlow", defaultArray))
} catch (e1: JSONException) {
try {
p.targetLow = JSONArray(defaultArray)
} catch (ignored: JSONException) {
}
aapsLogger.error("Exception", e1)
}
try {
p.targetHigh = JSONArray(sp.getString(localProfileNumbered + "targethigh", defaultArray)) p.targetHigh = JSONArray(sp.getString(localProfileNumbered + "targethigh", defaultArray))
} catch (e1: JSONException) {
try {
p.targetHigh = JSONArray(defaultArray)
} catch (ignored: JSONException) {
}
aapsLogger.error("Exception", e1)
}
profiles.add(p) profiles.add(p)
} catch (e: JSONException) {
aapsLogger.error("Exception", e)
} }
}
// create at least one profile if doesn't exist
if (profiles.size < 1) profiles.add(defaultProfile())
isEdited = false isEdited = false
numOfProfiles = profiles.size numOfProfiles = profiles.size
createAndStoreConvertedProfile() createAndStoreConvertedProfile()
} }
@Synchronized
fun loadFromStore(store: ProfileStore) {
try {
val newProfiles: ArrayList<SingleProfile> = ArrayList()
for (p in store.getProfileList()) {
store.getSpecificProfile(p.toString())?.let {
val sp = copyFrom(it, p.toString())
sp.name = p.toString()
newProfiles.add(sp)
}
}
if (newProfiles.size > 0) {
profiles = newProfiles
numOfProfiles = profiles.size
currentProfileIndex = 0
isEdited = false
createAndStoreConvertedProfile()
aapsLogger.debug(LTag.PROFILE, "Accepted ${profiles.size} profiles")
rxBus.send(EventLocalProfileChanged())
} else
aapsLogger.debug(LTag.PROFILE, "ProfileStore not accepted")
} catch (e: Exception) {
aapsLogger.error("Error loading ProfileStore", e)
}
}
private fun defaultProfile(): SingleProfile =
SingleProfile().also { p ->
p.name = Constants.LOCAL_PROFILE
p.mgdl = profileFunction.getUnits() == GlucoseUnit.MGDL
p.dia = Constants.defaultDIA
try {
p.ic = JSONArray(defaultArray)
p.isf = JSONArray(defaultArray)
p.basal = JSONArray(defaultArray)
p.targetLow = JSONArray(defaultArray)
p.targetHigh = JSONArray(defaultArray)
} catch (e: JSONException) {
aapsLogger.error("Exception", e)
}
}
fun copyFrom(pureProfile: PureProfile, newName: String): SingleProfile { fun copyFrom(pureProfile: PureProfile, newName: String): SingleProfile {
var verifiedName = newName var verifiedName = newName
if (rawProfile?.getSpecificProfile(newName) != null) { if (rawProfile?.getSpecificProfile(newName) != null) {
@ -364,7 +370,8 @@ class LocalProfilePlugin @Inject constructor(
} }
} }
if (numOfProfiles > 0) json.put("defaultProfile", currentProfile()?.name) if (numOfProfiles > 0) json.put("defaultProfile", currentProfile()?.name)
json.put("startDate", dateUtil.toISOAsUTC(dateUtil.now())) val startDate = sp.getLong(R.string.key_local_profile_last_change, dateUtil.now())
json.put("startDate", dateUtil.toISOAsUTC(startDate))
json.put("store", store) json.put("store", store)
} catch (e: JSONException) { } catch (e: JSONException) {
aapsLogger.error("Unhandled exception", e) aapsLogger.error("Unhandled exception", e)
@ -380,4 +387,44 @@ class LocalProfilePlugin @Inject constructor(
get() = rawProfile?.getDefaultProfile()?.let { get() = rawProfile?.getDefaultProfile()?.let {
DecimalFormatter.to2Decimal(ProfileSealed.Pure(it).percentageBasalSum()) + "U " DecimalFormatter.to2Decimal(ProfileSealed.Pure(it).percentageBasalSum()) + "U "
} ?: "INVALID" } ?: "INVALID"
// cannot be inner class because of needed injection
class NSProfileWorker(
context: Context,
params: WorkerParameters
) : Worker(context, params) {
@Inject lateinit var injector: HasAndroidInjector
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var dataWorker: DataWorker
@Inject lateinit var sp: SP
@Inject lateinit var config: Config
@Inject lateinit var localProfilePlugin: LocalProfilePlugin
init {
(context.applicationContext as HasAndroidInjector).androidInjector().inject(this)
}
override fun doWork(): Result {
val profileJson = dataWorker.pickupJSONObject(inputData.getLong(DataWorker.STORE_KEY, -1))
?: return Result.failure(workDataOf("Error" to "missing input data"))
if (sp.getBoolean(R.string.key_ns_receive_profile_store, false) || config.NSCLIENT) {
val store = ProfileStore(injector, profileJson, dateUtil)
val startDate = store.getStartDate()
val lastLocalChange = sp.getLong(R.string.key_local_profile_last_change, 0)
aapsLogger.debug(LTag.PROFILE, "Received profileStore: StartDate: $startDate Local last modification: $lastLocalChange")
@Suppress("LiftReturnOrAssignment")
if (startDate > lastLocalChange || startDate % 1000 == 0L) {// whole second means edited in NS
localProfilePlugin.loadFromStore(store)
aapsLogger.debug(LTag.PROFILE, "Received profileStore: $profileJson")
return Result.success(workDataOf("Data" to profileJson.toString().substring(0..min(5000, profileJson.length()))))
} else
return Result.success(workDataOf("Result" to "Unchanged. Ignoring"))
}
return Result.success(workDataOf("Result" to "Sync not enabled"))
}
}
} }

View file

@ -1,170 +0,0 @@
package info.nightscout.androidaps.plugins.profile.ns
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.ProfileSealed
import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.databinding.NsprofileFragmentBinding
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.profile.ns.events.EventNSProfileUpdateGUI
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import javax.inject.Inject
class NSProfileFragment : DaggerFragment() {
@Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var nsProfilePlugin: NSProfilePlugin
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var config: Config
private var disposable: CompositeDisposable = CompositeDisposable()
private var _binding: NsprofileFragmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
_binding = NsprofileFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.profileviewer.closeLayout.close.visibility = View.GONE // not needed for fragment
binding.profileswitch.setOnClickListener {
val name = binding.spinner.selectedItem?.toString() ?: ""
nsProfilePlugin.profile?.let { store ->
store.getSpecificProfile(name)?.let {
activity?.let { activity ->
OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.nsprofile),
resourceHelper.gs(R.string.activate_profile) + ": " + name + " ?", Runnable {
uel.log(Action.PROFILE_SWITCH, Sources.NSProfile,
ValueWithUnit.SimpleString(name),
ValueWithUnit.Percent(100))
profileFunction.createProfileSwitch(store, name, 0, 100, 0, dateUtil.now())
})
}
}
}
}
binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {
if (_binding == null) return
binding.profileviewer.invalidprofile.visibility = View.VISIBLE
binding.profileviewer.noprofile.visibility = View.VISIBLE
binding.profileviewer.units.text = ""
binding.profileviewer.dia.text = ""
binding.profileviewer.activeprofile.text = ""
binding.profileviewer.ic.text = ""
binding.profileviewer.isf.text = ""
binding.profileviewer.basal.text = ""
binding.profileviewer.basaltotal.text = ""
binding.profileviewer.target.text = ""
binding.profileswitch.visibility = View.GONE
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
if (_binding == null) return
val name = binding.spinner.getItemAtPosition(position).toString()
binding.profileswitch.visibility = View.GONE
nsProfilePlugin.profile?.let { store ->
store.getSpecificProfile(name)?.let { profile ->
if (_binding == null) return
val pss = ProfileSealed.Pure(profile)
binding.profileviewer.units.text = pss.units.asText
binding.profileviewer.dia.text = resourceHelper.gs(R.string.format_hours, pss.dia)
binding.profileviewer.activeprofile.text = name
binding.profileviewer.ic.text = pss.getIcList(resourceHelper, dateUtil)
binding.profileviewer.isf.text = pss.getIsfList(resourceHelper, dateUtil)
binding.profileviewer.basal.text = pss.getBasalList(resourceHelper, dateUtil)
binding.profileviewer.basaltotal.text = String.format(resourceHelper.gs(R.string.profile_total), DecimalFormatter.to2Decimal(pss.baseBasalSum()))
binding.profileviewer.target.text = pss.getTargetList(resourceHelper, dateUtil)
binding.profileviewer.basalGraph.show(pss)
if (pss.isValid("NSProfileFragment", activePlugin.activePump, config, resourceHelper, rxBus)) {
binding.profileviewer.invalidprofile.visibility = View.GONE
binding.profileswitch.visibility = View.VISIBLE
} else {
binding.profileviewer.invalidprofile.visibility = View.VISIBLE
binding.profileswitch.visibility = View.GONE
}
}
}
}
}
}
@Synchronized
override fun onResume() {
super.onResume()
disposable += rxBus
.toObservable(EventNSProfileUpdateGUI::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ updateGUI() }, fabricPrivacy::logException)
updateGUI()
}
@Synchronized
override fun onPause() {
super.onPause()
disposable.clear()
}
@Synchronized
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
@Synchronized
fun updateGUI() {
if (_binding == null) return
binding.profileviewer.noprofile.visibility = View.VISIBLE
nsProfilePlugin.profile?.let { profileStore ->
context?.let { context ->
val profileList = profileStore.getProfileList()
val adapter = ArrayAdapter(context, R.layout.spinner_centered, profileList)
binding.spinner.adapter = adapter
// set selected to actual profile
for (p in profileList.indices) {
if (profileList[p] == profileFunction.getProfileName())
binding.spinner.setSelection(p)
}
binding.profileviewer.noprofile.visibility = View.GONE
}
}
}
}

View file

@ -1,110 +0,0 @@
package info.nightscout.androidaps.plugins.profile.ns
import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.EventProfileStoreChanged
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.interfaces.ProfileSource
import info.nightscout.androidaps.interfaces.ProfileStore
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.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.plugins.profile.ns.events.EventNSProfileUpdateGUI
import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import org.json.JSONObject
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class NSProfilePlugin @Inject constructor(
injector: HasAndroidInjector,
aapsLogger: AAPSLogger,
private val rxBus: RxBusWrapper,
resourceHelper: ResourceHelper,
private val sp: SP,
private val dateUtil: DateUtil,
config: Config
) : PluginBase(PluginDescription()
.mainType(PluginType.PROFILE)
.fragmentClass(NSProfileFragment::class.java.name)
.pluginIcon(R.drawable.ic_nightscout_profile)
.pluginName(R.string.nsprofile)
.shortName(R.string.profileviewer_shortname)
.alwaysEnabled(config.NSCLIENT)
.alwaysVisible(config.NSCLIENT)
.showInList(!config.NSCLIENT)
.description(R.string.description_profile_nightscout),
aapsLogger, resourceHelper, injector
), ProfileSource {
override var profile: ProfileStore? = null
override val profileName: String?
get() = profile?.getDefaultProfileName()
override fun onStart() {
super.onStart()
loadNSProfile()
}
private fun storeNSProfile() {
sp.putString("profile", profile!!.data.toString())
aapsLogger.debug(LTag.PROFILE, "Storing profile")
}
private fun loadNSProfile() {
aapsLogger.debug(LTag.PROFILE, "Loading stored profile")
val profileString = sp.getStringOrNull("profile", null)
if (profileString != null) {
aapsLogger.debug(LTag.PROFILE, "Loaded profile: $profileString")
profile = ProfileStore(injector, JSONObject(profileString), dateUtil)
} else {
aapsLogger.debug(LTag.PROFILE, "Stored profile not found")
// force restart of nsclient to fetch profile
rxBus.send(EventNSClientRestart())
}
}
// cannot be inner class because of needed injection
class NSProfileWorker(
context: Context,
params: WorkerParameters
) : Worker(context, params) {
@Inject lateinit var injector: HasAndroidInjector
@Inject lateinit var nsProfilePlugin: NSProfilePlugin
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var dataWorker: DataWorker
init {
(context.applicationContext as HasAndroidInjector).androidInjector().inject(this)
}
override fun doWork(): Result {
val profileString = dataWorker.pickupJSONObject(inputData.getLong(DataWorker.STORE_KEY, -1))
?: return Result.failure(workDataOf("Error" to "missing input data"))
nsProfilePlugin.profile = ProfileStore(injector, profileString, dateUtil)
nsProfilePlugin.storeNSProfile()
if (nsProfilePlugin.isEnabled()) {
rxBus.send(EventProfileStoreChanged())
rxBus.send(EventNSProfileUpdateGUI())
}
aapsLogger.debug(LTag.PROFILE, "Received profileStore: ${nsProfilePlugin.profile}")
return Result.success()
}
}
}

View file

@ -1,5 +0,0 @@
package info.nightscout.androidaps.plugins.profile.ns.events
import info.nightscout.androidaps.events.EventUpdateGui
class EventNSProfileUpdateGUI : EventUpdateGui()

View file

@ -3,7 +3,6 @@ package info.nightscout.androidaps.plugins.pump.mdi
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.DetailedBolusInfo import info.nightscout.androidaps.data.DetailedBolusInfo
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.data.PumpEnactResult import info.nightscout.androidaps.data.PumpEnactResult
import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
@ -12,7 +11,6 @@ import info.nightscout.androidaps.plugins.common.ManufacturerType
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.InstanceId.instanceId import info.nightscout.androidaps.utils.InstanceId.instanceId
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
@ -61,7 +59,7 @@ class MDIPlugin @Inject constructor(
override fun waitForDisconnectionInSeconds(): Int = 0 override fun waitForDisconnectionInSeconds(): Int = 0
override fun stopConnecting() {} override fun stopConnecting() {}
override fun getPumpStatus(reason: String) {} override fun getPumpStatus(reason: String) {}
override fun setNewBasalProfile(profile: Profile): PumpEnactResult = PumpEnactResult(injector).success(true) override fun setNewBasalProfile(profile: Profile): PumpEnactResult = PumpEnactResult(injector).success(true).enacted(true)
override fun isThisProfileSet(profile: Profile): Boolean = false override fun isThisProfileSet(profile: Profile): Boolean = false
override fun lastDataTime(): Long = System.currentTimeMillis() override fun lastDataTime(): Long = System.currentTimeMillis()
override val baseBasalRate: Double = 0.0 override val baseBasalRate: Double = 0.0
@ -84,7 +82,7 @@ class MDIPlugin @Inject constructor(
pumpSerial = serialNumber()) pumpSerial = serialNumber())
if (detailedBolusInfo.carbs > 0) if (detailedBolusInfo.carbs > 0)
pumpSync.syncCarbsWithTimestamp( pumpSync.syncCarbsWithTimestamp(
timestamp = detailedBolusInfo.timestamp + T.mins(detailedBolusInfo.carbTime.toLong()).msecs(), timestamp = detailedBolusInfo.carbsTimestamp ?: detailedBolusInfo.timestamp,
amount = detailedBolusInfo.carbs, amount = detailedBolusInfo.carbs,
pumpId = null, pumpId = null,
pumpType = PumpType.MDI, pumpType = PumpType.MDI,

View file

@ -4,12 +4,12 @@ import android.os.SystemClock
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference import androidx.preference.SwitchPreference
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.DetailedBolusInfo import info.nightscout.androidaps.data.DetailedBolusInfo
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.data.PumpEnactResult import info.nightscout.androidaps.data.PumpEnactResult
import info.nightscout.androidaps.events.EventPreferenceChange import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.plannedRemainingMinutes
import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
@ -25,8 +25,6 @@ import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.InstanceId.instanceId import info.nightscout.androidaps.utils.InstanceId.instanceId
import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.TimeChangeType import info.nightscout.androidaps.utils.TimeChangeType
import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.plannedRemainingMinutes
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.sharedPreferences.SP
@ -139,7 +137,6 @@ open class VirtualPumpPlugin @Inject constructor(
override fun isHandshakeInProgress(): Boolean = false override fun isHandshakeInProgress(): Boolean = false
override fun connect(reason: String) { override fun connect(reason: String) {
//if (!Config.NSCLIENT) NSUpload.uploadDeviceStatus()
lastDataTime = System.currentTimeMillis() lastDataTime = System.currentTimeMillis()
} }
@ -152,16 +149,14 @@ open class VirtualPumpPlugin @Inject constructor(
override fun setNewBasalProfile(profile: Profile): PumpEnactResult { override fun setNewBasalProfile(profile: Profile): PumpEnactResult {
lastDataTime = System.currentTimeMillis() lastDataTime = System.currentTimeMillis()
rxBus.send(EventNewNotification(Notification(Notification.PROFILE_SET_OK, resourceHelper.gs(R.string.profile_set_ok), Notification.INFO, 60)))
// Do nothing here. we are using database profile // Do nothing here. we are using database profile
val result = PumpEnactResult(injector) return PumpEnactResult(injector).success(true).enacted(true)
result.success = true
val notification = Notification(Notification.PROFILE_SET_OK, resourceHelper.gs(R.string.profile_set_ok), Notification.INFO, 60)
rxBus.send(EventNewNotification(notification))
return result
} }
override fun isThisProfileSet(profile: Profile): Boolean { override fun isThisProfileSet(profile: Profile): Boolean {
return true val running = pumpSync.expectedPumpState().profile
return running?.isEqual(profile) ?: false
} }
override fun lastDataTime(): Long { override fun lastDataTime(): Long {
@ -211,7 +206,7 @@ open class VirtualPumpPlugin @Inject constructor(
pumpSerial = serialNumber()) pumpSerial = serialNumber())
if (detailedBolusInfo.carbs > 0) if (detailedBolusInfo.carbs > 0)
pumpSync.syncCarbsWithTimestamp( pumpSync.syncCarbsWithTimestamp(
timestamp = detailedBolusInfo.timestamp + T.mins(detailedBolusInfo.carbTime.toLong()).msecs(), timestamp = detailedBolusInfo.carbsTimestamp ?: detailedBolusInfo.timestamp,
amount = detailedBolusInfo.carbs, amount = detailedBolusInfo.carbs,
pumpId = null, pumpId = null,
pumpType = pumpType ?: PumpType.GENERIC_AAPS, pumpType = pumpType ?: PumpType.GENERIC_AAPS,
@ -400,7 +395,7 @@ open class VirtualPumpPlugin @Inject constructor(
aapsLogger.debug(LTag.PUMP, "Pump in configuration: $pumpType, PumpType object: $pumpTypeNew") aapsLogger.debug(LTag.PUMP, "Pump in configuration: $pumpType, PumpType object: $pumpTypeNew")
if (this.pumpType == pumpTypeNew) return if (this.pumpType == pumpTypeNew) return
aapsLogger.debug(LTag.PUMP, "New pump configuration found ($pumpTypeNew), changing from previous (${this.pumpType})") aapsLogger.debug(LTag.PUMP, "New pump configuration found ($pumpTypeNew), changing from previous (${this.pumpType})")
pumpDescription.setPumpDescription(pumpTypeNew) pumpDescription.fillFor(pumpTypeNew)
this.pumpType = pumpTypeNew this.pumpType = pumpTypeNew
} }

View file

@ -8,7 +8,6 @@ import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import androidx.work.workDataOf import androidx.work.workDataOf
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.RequestDexcomPermissionActivity import info.nightscout.androidaps.activities.RequestDexcomPermissionActivity
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
@ -18,10 +17,8 @@ import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.transactions.CgmSourceTransaction import info.nightscout.androidaps.database.transactions.CgmSourceTransaction
import info.nightscout.androidaps.interfaces.BgSource import info.nightscout.androidaps.extensions.fromConstant
import info.nightscout.androidaps.interfaces.PluginBase import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.logging.UserEntryLogger
@ -97,7 +94,7 @@ class DexcomPlugin @Inject constructor(
override fun doWork(): Result { override fun doWork(): Result {
var ret = Result.success() var ret = Result.success()
if (!dexcomPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() if (!dexcomPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled"))
val bundle = dataWorker.pickupBundle(inputData.getLong(DataWorker.STORE_KEY, -1)) val bundle = dataWorker.pickupBundle(inputData.getLong(DataWorker.STORE_KEY, -1))
?: return Result.failure(workDataOf("Error" to "missing input data")) ?: return Result.failure(workDataOf("Error" to "missing input data"))
try { try {
@ -126,11 +123,12 @@ class DexcomPlugin @Inject constructor(
meters.getBundle(i.toString())?.let { meters.getBundle(i.toString())?.let {
val timestamp = it.getLong("timestamp") * 1000 val timestamp = it.getLong("timestamp") * 1000
val now = dateUtil.now() val now = dateUtil.now()
val value = it.getInt("meterValue").toDouble()
if (timestamp > now - T.months(1).msecs() && timestamp < now) { if (timestamp > now - T.months(1).msecs() && timestamp < now) {
calibrations.add(CgmSourceTransaction.Calibration( calibrations.add(CgmSourceTransaction.Calibration(
timestamp = it.getLong("timestamp") * 1000, timestamp = it.getLong("timestamp") * 1000,
value = it.getInt("meterValue").toDouble(), value = value,
glucoseUnit = TherapyEvent.GlucoseUnit.MGDL glucoseUnit = TherapyEvent.GlucoseUnit.fromConstant(Profile.unit(value))
)) ))
} }
} }
@ -158,14 +156,14 @@ class DexcomPlugin @Inject constructor(
} }
result.sensorInsertionsInserted.forEach { result.sensorInsertionsInserted.forEach {
uel.log(Action.CAREPORTAL, uel.log(Action.CAREPORTAL,
Sources.BG, Sources.Dexcom,
ValueWithUnit.Timestamp(it.timestamp), ValueWithUnit.Timestamp(it.timestamp),
ValueWithUnit.TherapyEventType(it.type)) ValueWithUnit.TherapyEventType(it.type))
aapsLogger.debug(LTag.DATABASE, "Inserted sensor insertion $it") aapsLogger.debug(LTag.DATABASE, "Inserted sensor insertion $it")
} }
result.calibrationsInserted.forEach { result.calibrationsInserted.forEach {
uel.log(Action.CAREPORTAL, uel.log(Action.CAREPORTAL,
Sources.BG, Sources.Dexcom,
ValueWithUnit.Timestamp(it.timestamp), ValueWithUnit.Timestamp(it.timestamp),
ValueWithUnit.TherapyEventType(it.type)) ValueWithUnit.TherapyEventType(it.type))
aapsLogger.debug(LTag.DATABASE, "Inserted calibration $it") aapsLogger.debug(LTag.DATABASE, "Inserted calibration $it")

View file

@ -69,7 +69,7 @@ class EversensePlugin @Inject constructor(
override fun doWork(): Result { override fun doWork(): Result {
var ret = Result.success() var ret = Result.success()
if (!eversensePlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() if (!eversensePlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled"))
val bundle = dataWorker.pickupBundle(inputData.getLong(DataWorker.STORE_KEY, -1)) val bundle = dataWorker.pickupBundle(inputData.getLong(DataWorker.STORE_KEY, -1))
?: return Result.failure(workDataOf("Error" to "missing input data")) ?: return Result.failure(workDataOf("Error" to "missing input data"))
if (bundle.containsKey("currentCalibrationPhase")) aapsLogger.debug(LTag.BGSOURCE, "currentCalibrationPhase: " + bundle.getString("currentCalibrationPhase")) if (bundle.containsKey("currentCalibrationPhase")) aapsLogger.debug(LTag.BGSOURCE, "currentCalibrationPhase: " + bundle.getString("currentCalibrationPhase"))

View file

@ -56,7 +56,7 @@ class GlimpPlugin @Inject constructor(
override fun doWork(): Result { override fun doWork(): Result {
var ret = Result.success() var ret = Result.success()
if (!glimpPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() if (!glimpPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled"))
aapsLogger.debug(LTag.BGSOURCE, "Received Glimp Data: $inputData}") aapsLogger.debug(LTag.BGSOURCE, "Received Glimp Data: $inputData}")
val glucoseValues = mutableListOf<CgmSourceTransaction.TransactionGlucoseValue>() val glucoseValues = mutableListOf<CgmSourceTransaction.TransactionGlucoseValue>()
glucoseValues += CgmSourceTransaction.TransactionGlucoseValue( glucoseValues += CgmSourceTransaction.TransactionGlucoseValue(

View file

@ -5,12 +5,12 @@ import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import androidx.work.workDataOf import androidx.work.workDataOf
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.GlucoseValue import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.database.transactions.CgmSourceTransaction import info.nightscout.androidaps.database.transactions.CgmSourceTransaction
import info.nightscout.androidaps.interfaces.BgSource import info.nightscout.androidaps.interfaces.BgSource
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.PluginBase import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType import info.nightscout.androidaps.interfaces.PluginType
@ -115,7 +115,7 @@ class NSClientSourcePlugin @Inject constructor(
override fun doWork(): Result { override fun doWork(): Result {
var ret = Result.success() var ret = Result.success()
if (!nsClientSourcePlugin.isEnabled() && !sp.getBoolean(R.string.key_ns_autobackfill, true) && !dexcomPlugin.isEnabled()) return Result.success() if (!nsClientSourcePlugin.isEnabled() && !sp.getBoolean(R.string.key_ns_receive_cgm, true) && !dexcomPlugin.isEnabled()) return Result.success(workDataOf("Result" to "Sync not enabled"))
val sgvs = dataWorker.pickupJSONArray(inputData.getLong(DataWorker.STORE_KEY, -1)) val sgvs = dataWorker.pickupJSONArray(inputData.getLong(DataWorker.STORE_KEY, -1))
?: return Result.failure(workDataOf("Error" to "missing input data")) ?: return Result.failure(workDataOf("Error" to "missing input data"))

View file

@ -60,7 +60,7 @@ class PoctechPlugin @Inject constructor(
override fun doWork(): Result { override fun doWork(): Result {
var ret = Result.success() var ret = Result.success()
if (!poctechPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() if (!poctechPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled"))
aapsLogger.debug(LTag.BGSOURCE, "Received Poctech Data $inputData") aapsLogger.debug(LTag.BGSOURCE, "Received Poctech Data $inputData")
try { try {
val glucoseValues = mutableListOf<CgmSourceTransaction.TransactionGlucoseValue>() val glucoseValues = mutableListOf<CgmSourceTransaction.TransactionGlucoseValue>()

View file

@ -59,7 +59,7 @@ class TomatoPlugin @Inject constructor(
override fun doWork(): Result { override fun doWork(): Result {
var ret = Result.success() var ret = Result.success()
if (!tomatoPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() if (!tomatoPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled"))
val glucoseValues = mutableListOf<CgmSourceTransaction.TransactionGlucoseValue>() val glucoseValues = mutableListOf<CgmSourceTransaction.TransactionGlucoseValue>()
glucoseValues += CgmSourceTransaction.TransactionGlucoseValue( glucoseValues += CgmSourceTransaction.TransactionGlucoseValue(
timestamp = inputData.getLong("com.fanqies.tomatofn.Extras.Time", 0), timestamp = inputData.getLong("com.fanqies.tomatofn.Extras.Time", 0),

View file

@ -73,7 +73,7 @@ class XdripPlugin @Inject constructor(
override fun doWork(): Result { override fun doWork(): Result {
var ret = Result.success() var ret = Result.success()
if (!xdripPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() if (!xdripPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled"))
val bundle = dataWorker.pickupBundle(inputData.getLong(DataWorker.STORE_KEY, -1)) val bundle = dataWorker.pickupBundle(inputData.getLong(DataWorker.STORE_KEY, -1))
?: return Result.failure(workDataOf("Error" to "missing input data")) ?: return Result.failure(workDataOf("Error" to "missing input data"))

View file

@ -105,25 +105,10 @@ public class TreatmentService extends OrmLiteBaseService<DatabaseHelper> impleme
return wrapped.queryForAll(); return wrapped.queryForAll();
} }
public void delete(Treatment data) throws SQLException {
wrapped.delete(data);
openHumansUploader.enqueueTreatment(data, true);
}
public void create(Treatment data) throws SQLException {
wrapped.create(data);
openHumansUploader.enqueueTreatment(data);
}
public Treatment queryForId(long id) throws SQLException { public Treatment queryForId(long id) throws SQLException {
return wrapped.queryForId(id); return wrapped.queryForId(id);
} }
public void update(Treatment data) throws SQLException {
wrapped.update(data);
openHumansUploader.enqueueTreatment(data);
}
public QueryBuilder<Treatment, Long> queryBuilder() { public QueryBuilder<Treatment, Long> queryBuilder() {
return wrapped.queryBuilder(); return wrapped.queryBuilder();
} }

View file

@ -26,7 +26,6 @@ import info.nightscout.androidaps.interfaces.TreatmentServiceInterface;
import info.nightscout.androidaps.interfaces.TreatmentsInterface; import info.nightscout.androidaps.interfaces.TreatmentsInterface;
import info.nightscout.androidaps.logging.AAPSLogger; import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.plugins.bus.RxBusWrapper; import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload;
import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.FabricPrivacy;
import info.nightscout.androidaps.utils.resources.ResourceHelper; import info.nightscout.androidaps.utils.resources.ResourceHelper;
@ -37,12 +36,10 @@ import io.reactivex.disposables.CompositeDisposable;
@Singleton @Singleton
public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface { public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface {
private final AapsSchedulers aapsSchedulers;
private final SP sp; private final SP sp;
private final RxBusWrapper rxBus; private final RxBusWrapper rxBus;
private final ProfileFunction profileFunction; private final ProfileFunction profileFunction;
private final ActivePlugin activePlugin; private final ActivePlugin activePlugin;
private final NSUpload nsUpload;
private final FabricPrivacy fabricPrivacy; private final FabricPrivacy fabricPrivacy;
private final DateUtil dateUtil; private final DateUtil dateUtil;
private final DatabaseHelperInterface databaseHelper; private final DatabaseHelperInterface databaseHelper;
@ -63,7 +60,6 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface
SP sp, SP sp,
ProfileFunction profileFunction, ProfileFunction profileFunction,
ActivePlugin activePlugin, ActivePlugin activePlugin,
NSUpload nsUpload,
FabricPrivacy fabricPrivacy, FabricPrivacy fabricPrivacy,
DateUtil dateUtil, DateUtil dateUtil,
DatabaseHelperInterface databaseHelper, DatabaseHelperInterface databaseHelper,
@ -71,7 +67,6 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface
) { ) {
super(new PluginDescription() super(new PluginDescription()
.mainType(PluginType.TREATMENT) .mainType(PluginType.TREATMENT)
.fragmentClass(TreatmentsFragment.class.getName())
.pluginIcon(R.drawable.ic_treatments) .pluginIcon(R.drawable.ic_treatments)
.pluginName(R.string.treatments) .pluginName(R.string.treatments)
.shortName(R.string.treatments_shortname) .shortName(R.string.treatments_shortname)
@ -81,13 +76,11 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface
aapsLogger, resourceHelper, injector aapsLogger, resourceHelper, injector
); );
this.rxBus = rxBus; this.rxBus = rxBus;
this.aapsSchedulers = aapsSchedulers;
this.sp = sp; this.sp = sp;
this.profileFunction = profileFunction; this.profileFunction = profileFunction;
this.activePlugin = activePlugin; this.activePlugin = activePlugin;
this.fabricPrivacy = fabricPrivacy; this.fabricPrivacy = fabricPrivacy;
this.dateUtil = dateUtil; this.dateUtil = dateUtil;
this.nsUpload = nsUpload;
this.databaseHelper = databaseHelper; this.databaseHelper = databaseHelper;
this.repository = repository; this.repository = repository;
} }

View file

@ -86,7 +86,6 @@ open class CommandQueue @Inject constructor(
ErrorHelperActivity.runAlarm(context, result.comment, resourceHelper.gs(R.string.failedupdatebasalprofile), R.raw.boluserror) ErrorHelperActivity.runAlarm(context, result.comment, resourceHelper.gs(R.string.failedupdatebasalprofile), R.raw.boluserror)
} }
if (result.enacted) { if (result.enacted) {
rxBus.send(EventNewBasalProfile())
repository.createEffectiveProfileSwitch( repository.createEffectiveProfileSwitch(
EffectiveProfileSwitch( EffectiveProfileSwitch(
timestamp = dateUtil.now(), timestamp = dateUtil.now(),
@ -104,6 +103,7 @@ open class CommandQueue @Inject constructor(
insulinConfiguration = it.insulinConfiguration insulinConfiguration = it.insulinConfiguration
) )
) )
rxBus.send(EventNewBasalProfile())
} }
} }
}) })
@ -566,11 +566,12 @@ open class CommandQueue @Inject constructor(
return HtmlHelper.fromHtml(s) return HtmlHelper.fromHtml(s)
} }
override fun isThisProfileSet(profile: Profile): Boolean { override fun isThisProfileSet(requestedProfile: Profile): Boolean {
val result = activePlugin.activePump.isThisProfileSet(profile) val runningProfile = profileFunction.getProfile() ?: return false
val result = activePlugin.activePump.isThisProfileSet(requestedProfile) && requestedProfile.isEqual(runningProfile)
if (!result) { if (!result) {
aapsLogger.debug(LTag.PUMPQUEUE, "Current profile: ${profileFunction.getProfile()}") aapsLogger.debug(LTag.PUMPQUEUE, "Current profile: ${profileFunction.getProfile()}")
aapsLogger.debug(LTag.PUMPQUEUE, "New profile: $profile") aapsLogger.debug(LTag.PUMPQUEUE, "New profile: $requestedProfile")
} }
return result return result
} }

View file

@ -6,6 +6,7 @@ import info.nightscout.androidaps.data.PumpEnactResult
import info.nightscout.androidaps.database.ValueWrapper import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.CommandQueueProvider import info.nightscout.androidaps.interfaces.CommandQueueProvider
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.PluginType import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
@ -25,9 +26,10 @@ class CommandSetProfile constructor(
@Inject lateinit var activePlugin: ActivePlugin @Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var dateUtil: DateUtil @Inject lateinit var dateUtil: DateUtil
@Inject lateinit var commandQueue: CommandQueueProvider @Inject lateinit var commandQueue: CommandQueueProvider
@Inject lateinit var config: Config
override fun execute() { override fun execute() {
if (commandQueue.isThisProfileSet(profile)) { if (commandQueue.isThisProfileSet(profile) && repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing) {
aapsLogger.debug(LTag.PUMPQUEUE, "Correct profile already set. profile: $profile") aapsLogger.debug(LTag.PUMPQUEUE, "Correct profile already set. profile: $profile")
callback?.result(PumpEnactResult(injector).success(true).enacted(false))?.run() callback?.result(PumpEnactResult(injector).success(true).enacted(false))?.run()
return return
@ -37,7 +39,7 @@ class CommandSetProfile constructor(
callback?.result(r)?.run() callback?.result(r)?.run()
// Send SMS notification if ProfileSwitch is coming from NS // Send SMS notification if ProfileSwitch is coming from NS
val profileSwitch = repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet() val profileSwitch = repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet()
if (profileSwitch is ValueWrapper.Existing && r.enacted && hasNsId) { if (profileSwitch is ValueWrapper.Existing && r.enacted && hasNsId && !config.NSCLIENT) {
if (smsCommunicatorPlugin.isEnabled(PluginType.GENERAL)) if (smsCommunicatorPlugin.isEnabled(PluginType.GENERAL))
smsCommunicatorPlugin.sendNotificationToAllNumbers(resourceHelper.gs(R.string.profile_set_ok)) smsCommunicatorPlugin.sendNotificationToAllNumbers(resourceHelper.gs(R.string.profile_set_ok))
} }

View file

@ -119,17 +119,18 @@ class KeepAliveReceiver : DaggerBroadcastReceiver() {
private fun checkPump() { private fun checkPump() {
val pump = activePlugin.activePump val pump = activePlugin.activePump
val ps = profileFunction.getRequestedProfile() ?: return val ps = profileFunction.getRequestedProfile() ?: return
val profile = ProfileSealed.PS(ps) val requestedProfile = ProfileSealed.PS(ps)
val runningProfile = profileFunction.getProfile()
val lastConnection = pump.lastDataTime() val lastConnection = pump.lastDataTime()
val isStatusOutdated = lastConnection + STATUS_UPDATE_FREQUENCY < System.currentTimeMillis() val isStatusOutdated = lastConnection + STATUS_UPDATE_FREQUENCY < System.currentTimeMillis()
val isBasalOutdated = abs(profile.getBasal() - pump.baseBasalRate) > pump.pumpDescription.basalStep val isBasalOutdated = abs(requestedProfile.getBasal() - pump.baseBasalRate) > pump.pumpDescription.basalStep
aapsLogger.debug(LTag.CORE, "Last connection: " + dateUtil.dateAndTimeString(lastConnection)) aapsLogger.debug(LTag.CORE, "Last connection: " + dateUtil.dateAndTimeString(lastConnection))
// sometimes keep alive broadcast stops // sometimes keep alive broadcast stops
// as as workaround test if readStatus was requested before an alarm is generated // as as workaround test if readStatus was requested before an alarm is generated
if (lastReadStatus != 0L && lastReadStatus > System.currentTimeMillis() - T.mins(5).msecs()) { if (lastReadStatus != 0L && lastReadStatus > System.currentTimeMillis() - T.mins(5).msecs()) {
localAlertUtils.checkPumpUnreachableAlarm(lastConnection, isStatusOutdated, loopPlugin.isDisconnected) localAlertUtils.checkPumpUnreachableAlarm(lastConnection, isStatusOutdated, loopPlugin.isDisconnected)
} }
if (!pump.isThisProfileSet(profile) && !commandQueue.isRunning(Command.CommandType.BASAL_PROFILE)) { if (runningProfile == null || ((!pump.isThisProfileSet(requestedProfile) || !requestedProfile.isEqual(runningProfile)) && !commandQueue.isRunning(Command.CommandType.BASAL_PROFILE))) {
rxBus.send(EventProfileSwitchChanged()) rxBus.send(EventProfileSwitchChanged())
} else if (isStatusOutdated && !pump.isBusy()) { } else if (isStatusOutdated && !pump.isBusy()) {
lastReadStatus = System.currentTimeMillis() lastReadStatus = System.currentTimeMillis()

View file

@ -19,11 +19,8 @@ import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesFragm
import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin
import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus
import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientService
import info.nightscout.androidaps.plugins.profile.local.LocalProfileFragment import info.nightscout.androidaps.plugins.profile.local.LocalProfileFragment
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.profile.ns.NSProfileFragment
import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin
import info.nightscout.androidaps.plugins.pump.common.events.EventRileyLinkDeviceStatusChange import info.nightscout.androidaps.plugins.pump.common.events.EventRileyLinkDeviceStatusChange
import info.nightscout.androidaps.plugins.pump.omnipod.dash.OmnipodDashPumpPlugin import info.nightscout.androidaps.plugins.pump.omnipod.dash.OmnipodDashPumpPlugin
import info.nightscout.androidaps.plugins.pump.omnipod.eros.OmnipodErosPumpPlugin import info.nightscout.androidaps.plugins.pump.omnipod.eros.OmnipodErosPumpPlugin
@ -53,7 +50,6 @@ class SWDefinition @Inject constructor(
private val configBuilder: ConfigBuilder, private val configBuilder: ConfigBuilder,
private val loopPlugin: LoopPlugin, private val loopPlugin: LoopPlugin,
private val nsClientPlugin: NSClientPlugin, private val nsClientPlugin: NSClientPlugin,
private val nsProfilePlugin: NSProfilePlugin,
private val importExportPrefs: ImportExportPrefs, private val importExportPrefs: ImportExportPrefs,
private val androidPermission: AndroidPermission, private val androidPermission: AndroidPermission,
private val cryptoUtil: CryptoUtil, private val cryptoUtil: CryptoUtil,
@ -189,8 +185,8 @@ class SWDefinition @Inject constructor(
.label(R.string.status) .label(R.string.status)
.initialStatus(nsClientPlugin.status) .initialStatus(nsClientPlugin.status)
) )
.validator { nsClientPlugin.nsClientService != null && NSClientService.isConnected && NSClientService.hasWriteAuth } .validator { nsClientPlugin.nsClientService?.isConnected == true && nsClientPlugin.nsClientService?.hasWriteAuth == true }
.visibility { !(nsClientPlugin.nsClientService != null && NSClientService.isConnected && NSClientService.hasWriteAuth) } .visibility { !(nsClientPlugin.nsClientService?.isConnected == true && nsClientPlugin.nsClientService?.hasWriteAuth == true) }
private val screenPatientName = SWScreen(injector, R.string.patient_name) private val screenPatientName = SWScreen(injector, R.string.patient_name)
.skippable(true) .skippable(true)
.add(SWInfoText(injector) .add(SWInfoText(injector)
@ -254,27 +250,14 @@ class SWDefinition @Inject constructor(
.option(PluginType.BGSOURCE, R.string.configbuilder_bgsource_description) .option(PluginType.BGSOURCE, R.string.configbuilder_bgsource_description)
.label(R.string.configbuilder_bgsource)) .label(R.string.configbuilder_bgsource))
.add(SWBreak(injector)) .add(SWBreak(injector))
private val screenProfile = SWScreen(injector, R.string.configbuilder_profile)
.skippable(false)
.add(SWInfoText(injector)
.label(R.string.setupwizard_profile_description))
.add(SWBreak(injector))
.add(SWPlugin(injector, this)
.option(PluginType.PROFILE, R.string.configbuilder_profile_description)
.label(R.string.configbuilder_profile))
private val screenNsProfile = SWScreen(injector, R.string.nsprofile)
.skippable(false)
.add(SWInfoText(injector)
.label(R.string.adjustprofileinns))
.add(SWFragment(injector, this)
.add(NSProfileFragment()))
.validator { nsProfilePlugin.profile?.getDefaultProfile()?.let { ProfileSealed.Pure(it).isValid("StartupWizard", activePlugin.activePump, config, resourceHelper, rxBus) } ?: false }
.visibility { nsProfilePlugin.isEnabled() }
private val screenLocalProfile = SWScreen(injector, R.string.localprofile) private val screenLocalProfile = SWScreen(injector, R.string.localprofile)
.skippable(false) .skippable(false)
.add(SWFragment(injector, this) .add(SWFragment(injector, this)
.add(LocalProfileFragment())) .add(LocalProfileFragment()))
.validator { localProfilePlugin.profile?.getDefaultProfile()?.let { ProfileSealed.Pure(it).isValid("StartupWizard", activePlugin.activePump, config, resourceHelper, rxBus) } ?: false } .validator {
localProfilePlugin.profile?.getDefaultProfile()?.let { ProfileSealed.Pure(it).isValid("StartupWizard", activePlugin.activePump, config, resourceHelper, rxBus) }
?: false
}
.visibility { localProfilePlugin.isEnabled() } .visibility { localProfilePlugin.isEnabled() }
private val screenProfileSwitch = SWScreen(injector, R.string.careportal_profileswitch) private val screenProfileSwitch = SWScreen(injector, R.string.careportal_profileswitch)
.skippable(false) .skippable(false)
@ -399,8 +382,6 @@ class SWDefinition @Inject constructor(
.add(screenAge) .add(screenAge)
.add(screenInsulin) .add(screenInsulin)
.add(screenBgSource) .add(screenBgSource)
.add(screenProfile)
.add(screenNsProfile)
.add(screenLocalProfile) .add(screenLocalProfile)
.add(screenProfileSwitch) .add(screenProfileSwitch)
.add(screenPump) .add(screenPump)
@ -428,8 +409,6 @@ class SWDefinition @Inject constructor(
.add(screenAge) .add(screenAge)
.add(screenInsulin) .add(screenInsulin)
.add(screenBgSource) .add(screenBgSource)
.add(screenProfile)
.add(screenNsProfile)
.add(screenLocalProfile) .add(screenLocalProfile)
.add(screenProfileSwitch) .add(screenProfileSwitch)
.add(screenPump) .add(screenPump)

View file

@ -17,13 +17,14 @@ class JSONFormatter @Inject constructor(
private val aapsLogger: AAPSLogger private val aapsLogger: AAPSLogger
) { ) {
@kotlin.ExperimentalStdlibApi
fun format(jsonString: String?): Spanned { fun format(jsonString: String?): Spanned {
jsonString ?: return fromHtml("") jsonString ?: return fromHtml("")
val visitor = JsonVisitor(1, '\t') val visitor = JsonVisitor(1, '\t')
return try { return try {
when { when {
jsonString == "undefined" -> fromHtml("undefined") jsonString == "undefined" -> fromHtml("undefined")
jsonString.toByteArray()[0] == '['.toByte() -> fromHtml(visitor.visit(JSONArray(jsonString), 0)) jsonString.toByteArray()[0] == '['.code.toByte() -> fromHtml(visitor.visit(JSONArray(jsonString), 0))
else -> fromHtml(visitor.visit(JSONObject(jsonString), 0)) else -> fromHtml(visitor.visit(JSONObject(jsonString), 0))
} }
} catch (e: JSONException) { } catch (e: JSONException) {

View file

@ -10,9 +10,12 @@ class TrendCalculator @Inject constructor(
private val repository: AppRepository private val repository: AppRepository
) { ) {
fun getTrendArrow(glucoseValue: GlucoseValue): GlucoseValue.TrendArrow = fun getTrendArrow(glucoseValue: GlucoseValue?): GlucoseValue.TrendArrow =
if (glucoseValue.trendArrow != GlucoseValue.TrendArrow.NONE) glucoseValue.trendArrow when {
else calculateDirection(glucoseValue) glucoseValue?.trendArrow == null -> GlucoseValue.TrendArrow.NONE
glucoseValue.trendArrow != GlucoseValue.TrendArrow.NONE -> glucoseValue.trendArrow
else -> calculateDirection(glucoseValue)
}
private fun calculateDirection(glucoseValue: GlucoseValue): GlucoseValue.TrendArrow { private fun calculateDirection(glucoseValue: GlucoseValue): GlucoseValue.TrendArrow {

View file

@ -12,6 +12,7 @@ import info.nightscout.androidaps.data.DetailedBolusInfo
import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.BolusCalculatorResult import info.nightscout.androidaps.database.entities.BolusCalculatorResult
import info.nightscout.androidaps.database.entities.OfflineEvent
import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.entities.TemporaryTarget import info.nightscout.androidaps.database.entities.TemporaryTarget
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
@ -374,7 +375,7 @@ class BolusWizard @Inject constructor(
if (useSuperBolus) { if (useSuperBolus) {
uel.log(Action.SUPERBOLUS_TBR, Sources.WizardDialog) uel.log(Action.SUPERBOLUS_TBR, Sources.WizardDialog)
if (loopPlugin.isEnabled(PluginType.LOOP)) { if (loopPlugin.isEnabled(PluginType.LOOP)) {
loopPlugin.superBolusTo(System.currentTimeMillis() + 2 * 60L * 60 * 1000) loopPlugin.goToZeroTemp(2 * 60, profile, OfflineEvent.Reason.SUPER_BOLUS)
rxBus.send(EventRefreshOverview("WizardDialog")) rxBus.send(EventRefreshOverview("WizardDialog"))
} }
@ -408,7 +409,7 @@ class BolusWizard @Inject constructor(
context = ctx context = ctx
mgdlGlucose = Profile.toMgdl(bg, profile.units) mgdlGlucose = Profile.toMgdl(bg, profile.units)
glucoseType = DetailedBolusInfo.MeterType.MANUAL glucoseType = DetailedBolusInfo.MeterType.MANUAL
carbTime = this@BolusWizard.carbTime carbsTimestamp = dateUtil.now() + T.mins(this@BolusWizard.carbTime.toLong()).msecs()
bolusCalculatorResult = createBolusCalculatorResult() bolusCalculatorResult = createBolusCalculatorResult()
notes = this@BolusWizard.notes notes = this@BolusWizard.notes
if (insulin > 0 || carbs > 0) { if (insulin > 0 || carbs > 0) {

View file

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

View file

@ -51,6 +51,7 @@
<LinearLayout <LinearLayout
android:id="@+id/overview_loop"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">

Some files were not shown because too many files have changed in this diff Show more