Merge branch 'nightscout:dev' into dev

This commit is contained in:
bubbledevteam 2021-10-05 20:16:08 +08:00 committed by GitHub
commit b57ca6f94c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1619 changed files with 60155 additions and 30621 deletions

View file

@ -68,6 +68,6 @@ workflows:
# 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:
dotests:
jobs:
- build-and-test

5
.editorconfig Normal file
View file

@ -0,0 +1,5 @@
root = true
[*{kt,kts}]
disabled_rules=no-wildcard-imports

2
.gitignore vendored
View file

@ -11,8 +11,6 @@ build/
.idea/*
!.idea/codeStyles/
full/
debug/
release/
app/com.crashlytics.settings.json
app/session_analytics.tap
.project

View file

@ -1,11 +1,13 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="AUTODETECT_INDENTS" value="false" />
<option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
<JetCodeStyleSettings>
<option name="ALIGN_IN_COLUMNS_CASE_BRANCH" value="true" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="6" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="6" />
<option name="BLANK_LINES_AROUND_BLOCK_WHEN_BRANCHES" value="1" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="METHOD_ANNOTATION_WRAP" value="0" />
@ -124,14 +126,22 @@
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="RIGHT_MARGIN" value="120" />
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="1" />
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
<option name="ASSIGNMENT_WRAP" value="5" />
<option name="METHOD_ANNOTATION_WRAP" value="5" />
<option name="FIELD_ANNOTATION_WRAP" value="0" />
<option name="CLASS_ANNOTATION_WRAP" value="1" />
<option name="FIELD_ANNOTATION_WRAP" value="1" />
<option name="PARAMETER_ANNOTATION_WRAP" value="1" />
<option name="VARIABLE_ANNOTATION_WRAP" value="1" />
<option name="ENUM_CONSTANTS_WRAP" value="5" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>

View file

@ -2,6 +2,7 @@
<dictionary name="project-dictionary">
<words>
<w>aaps</w>
<w>abcdef</w>
<w>acked</w>
<w>actionstring</w>
<w>allowednumbers</w>
@ -14,8 +15,10 @@
<w>basals</w>
<w>bgcheck</w>
<w>bgsource</w>
<w>boluscalc</w>
<w>bolusing</w>
<w>carb</w>
<w>carbratio</w>
<w>carbs</w>
<w>carbsreq</w>
<w>careportal</w>
@ -46,6 +49,7 @@
<w>medtronic</w>
<w>mgdl</w>
<w>mmol</w>
<w>motol</w>
<w>multiwave</w>
<w>netinsulin</w>
<w>netratio</w>
@ -55,9 +59,14 @@
<w>okcancel</w>
<w>omnipod</w>
<w>openaps</w>
<w>openhumans</w>
<w>oref</w>
<w>otpauth</w>
<w>passcode</w>
<w>pdus</w>
<w>philoul</w>
<w>poctech</w>
<w>pred</w>
<w>profileswitch</w>
<w>pumpbtcomm</w>
<w>quickwizard</w>
@ -66,14 +75,18 @@
<w>refresheventsfromnightscout</w>
<w>rileylink</w>
<w>roboelectric</w>
<w>sgvs</w>
<w>sitechange</w>
<w>smscommunicator</w>
<w>sntp</w>
<w>sooil</w>
<w>soundid</w>
<w>splitted</w>
<w>ssid</w>
<w>superbolus</w>
<w>targethigh</w>
<w>targetlow</w>
<w>tbrs</w>
<w>tdds</w>
<w>tempbasal</w>
<w>tempbasals</w>
@ -82,9 +95,11 @@
<w>tidepool</w>
<w>timeshift</w>
<w>tirs</w>
<w>totp</w>
<w>uart</w>
<w>wizzardpage</w>
<w>xdrip</w>
<w>xstream</w>
<w>ypso</w>
<w>ypsomed</w>
<w>ypsopump</w>

View file

@ -1,26 +1,24 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-allopen'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.hiya.jacoco-android'
apply plugin: 'com.google.firebase.crashlytics'
apply from: "${project.rootDir}/gradle/android_dependencies.gradle"
jacoco {
toolVersion = "0.8.6"
}
tasks.withType(Test) {
jacoco.includeNoLocationClasses = true
jacoco.excludes = ['jdk.internal.*']
}
apply from: "${project.rootDir}/gradle/jacoco_global.gradle"
repositories {
mavenCentral()
google()
}
allOpen {
// allows mocking for classes w/o directly opening them for release builds
annotation 'info.nightscout.androidaps.annotations.OpenForTesting'
}
def generateGitBuild = { ->
StringBuilder stringBuilder = new StringBuilder()
try {
@ -111,7 +109,7 @@ android {
defaultConfig {
multiDexEnabled true
versionCode 1500
version "2.8.2.1-dev-e4"
version "2.8.2.4-dev"
buildConfigField "String", "VERSION", '"' + version + '"'
buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"'
buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"'
@ -186,19 +184,20 @@ dependencies {
implementation project(':danars')
implementation project(':danar')
implementation project(':insight')
implementation project(':pump-common')
implementation project(':rileylink')
implementation project(':medtronic')
implementation project(':omnipod-common')
implementation project(':omnipod-eros')
implementation project(':omnipod-dash')
implementation project(':diaconn')
implementation project(':openhumans')
implementation fileTree(include: ['*.jar'], dir: 'libs')
/* Dagger2 - We are going to use dagger.android which includes
* support for Activity and fragment injection so we need to include
* the following dependencies */
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
annotationProcessor "com.google.dagger:dagger-android-processor:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version"

View file

@ -1,42 +1,7 @@
package info.nightscout.androidaps
import android.os.SystemClock
import android.view.View
import android.view.ViewGroup
import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.scrollTo
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withClassName
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withTagValue
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
import androidx.test.rule.GrantPermissionRule
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin
import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin
import info.nightscout.androidaps.plugins.source.RandomBgPlugin
import info.nightscout.androidaps.setupwizard.SetupWizardActivity
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.extensions.isRunningTest
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers
import org.hamcrest.TypeSafeMatcher
import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@LargeTest

View file

@ -52,7 +52,8 @@
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
<activity android:name=".MainActivity">
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.MAIN" />
@ -60,7 +61,8 @@
</intent-filter>
</activity>
<activity android:name=".activities.PreferencesActivity" />
<activity android:name=".plugins.general.overview.activities.QuickWizardListActivity">
<activity android:name=".plugins.general.overview.activities.QuickWizardListActivity"
android:exported="false">
<intent-filter>
<action android:name="info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity" />
@ -68,7 +70,7 @@
</intent-filter>
</activity>
<activity android:name=".plugins.general.maintenance.activities.PrefImportListActivity" />
<activity android:name=".historyBrowser.HistoryBrowseActivity" />
<activity android:name=".activities.HistoryBrowseActivity" />
<activity android:name=".activities.TreatmentsActivity" />
<activity android:name=".activities.SurveyActivity" />
<activity android:name=".activities.ProfileHelperActivity"
@ -115,11 +117,11 @@
</intent-filter>
</receiver>
<!-- Receiver keepalive, scheduled every 30 min -->
<!-- Receiver keep alive, scheduled every 30 min -->
<receiver android:name=".receivers.KeepAliveReceiver" />
<!-- Receive ignore 5m, 15m, 30m requests for carb notifications -->
<receiver android:name=".plugins.aps.loop.CarbSuggestionReceiver"></receiver>
<receiver android:name=".plugins.aps.loop.CarbSuggestionReceiver" />
<!-- Auto start -->
<receiver
@ -241,20 +243,6 @@
</intent-filter>
</activity>
<activity android:name=".plugins.general.openhumans.OpenHumansLoginActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="setup-openhumans"
android:scheme="androidaps" />
</intent-filter>
</activity>
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
</application>

View file

@ -1,7 +1,7 @@
<configuration>
<!-- Create a file appender for a log in the application's data directory -->
<property name="EXT_FILES_DIR" scope="context"
value="${EXT_DIR:-/sdcard}/Android/data/${PACKAGE_NAME}/files" />
value="${EXT_DIR:-/sdcard}/AAPS/logs/${PACKAGE_NAME}" />
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${EXT_FILES_DIR}/AndroidAPS.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

View file

@ -34,7 +34,7 @@ import info.nightscout.androidaps.databinding.ActivityMainBinding
import info.nightscout.androidaps.events.EventAppExit
import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.events.EventRebuildTabs
import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity
import info.nightscout.androidaps.activities.HistoryBrowseActivity
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.IconsProvider
@ -95,7 +95,6 @@ class MainActivity : NoSplashAppCompatActivity() {
private lateinit var binding: ActivityMainBinding
@kotlin.ExperimentalStdlibApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Iconify.with(FontAwesomeModule())
@ -166,7 +165,7 @@ class MainActivity : NoSplashAppCompatActivity() {
actionBarDrawerToggle.syncState()
}
public override fun onDestroy() {
override fun onDestroy() {
super.onDestroy()
disposable.clear()
}
@ -361,7 +360,6 @@ class MainActivity : NoSplashAppCompatActivity() {
// Correct place for calling setUserStats() would be probably MainApp
// but we need to have it called at least once a day. Thus this location
@kotlin.ExperimentalStdlibApi
private fun setUserStats() {
if (!fabricPrivacy.fabricEnabled()) return
val closedLoopEnabled = if (constraintChecker.isClosedLoopAllowed().value()) "CLOSED_LOOP_ENABLED" else "CLOSED_LOOP_DISABLED"

View file

@ -6,7 +6,6 @@ import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.wifi.WifiManager
import android.os.Build
import com.j256.ormlite.android.apptools.OpenHelperManager
import dagger.android.AndroidInjector
import dagger.android.DaggerApplication
import info.nightscout.androidaps.database.AppRepository
@ -15,11 +14,10 @@ import info.nightscout.androidaps.database.entities.UserEntry
import info.nightscout.androidaps.database.transactions.InsertIfNewByTimestampTherapyEventTransaction
import info.nightscout.androidaps.database.transactions.VersionChangeTransaction
import info.nightscout.androidaps.db.CompatDBHelper
import info.nightscout.androidaps.db.DatabaseHelper
import info.nightscout.androidaps.db.StaticInjector
import info.nightscout.androidaps.di.StaticInjector
import info.nightscout.androidaps.dependencyInjection.DaggerAppComponent
import info.nightscout.androidaps.interfaces.ConfigBuilder
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.ConfigBuilder
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
@ -34,6 +32,7 @@ import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver
import info.nightscout.androidaps.utils.ActivityMonitor
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.locale.LocaleHelper.update
import info.nightscout.androidaps.utils.protection.PasswordCheck
import info.nightscout.androidaps.utils.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
@ -58,12 +57,12 @@ class MainApp : DaggerApplication() {
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var staticInjector: StaticInjector// TODO avoid , here fake only to initialize
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var passwordCheck: PasswordCheck
override fun onCreate() {
super.onCreate()
aapsLogger.debug("onCreate")
update(this)
dbHelper = OpenHelperManager.getHelper(this, DatabaseHelper::class.java)
var gitRemote: String? = BuildConfig.REMOTE
var commitHash: String? = BuildConfig.HEAD
@ -90,6 +89,7 @@ class MainApp : DaggerApplication() {
keepAliveManager.setAlarm(this)
doMigrations()
uel.log(UserEntry.Action.START_AAPS, UserEntry.Sources.Aaps)
passwordCheck.passwordResetCheck(this)
}
private fun doMigrations() {
@ -134,9 +134,4 @@ class MainApp : DaggerApplication() {
keepAliveManager.cancelAlarm(this)
super.onTerminate()
}
companion object {
lateinit var dbHelper: DatabaseHelper
}
}

View file

@ -0,0 +1,406 @@
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 {
adjustTimeRange(overviewData.fromTime - T.hours(rangeToDisplay.toLong()).msecs())
loadAll("onClickLeft")
}
binding.right.setOnClickListener {
adjustTimeRange(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()
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R)
display?.getRealMetrics(dm)
else
@Suppress("DEPRECATION") 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)
}
}
override fun onPause() {
super.onPause()
disposable.clear()
iobCobCalculator.stopCalculation("onPause")
}
@Synchronized
override fun onDestroy() {
destroyed = true
super.onDestroy()
}
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) {
updateDate()
Thread {
overviewData.prepareBgData("$from")
overviewData.prepareTreatmentsData(from)
rxBus.send(EventRefreshOverview("loadAll_$from"))
overviewData.prepareTemporaryTargetData(from)
rxBus.send(EventRefreshOverview("loadAll_$from"))
overviewData.prepareBasalData(from)
rxBus.send(EventRefreshOverview(from))
aapsLogger.debug(LTag.UI, "loadAll $from finished")
runCalculation(from)
}.start()
}
private fun setTime(start: Long) {
GregorianCalendar().also { calendar ->
calendar.timeInMillis = start
calendar[Calendar.MILLISECOND] = 0
calendar[Calendar.SECOND] = 0
calendar[Calendar.MINUTE] = 0
calendar[Calendar.HOUR_OF_DAY] -= (calendar[Calendar.HOUR_OF_DAY] % rangeToDisplay)
adjustTimeRange(calendar.timeInMillis)
}
}
private fun adjustTimeRange(start: Long) {
overviewData.fromTime = start
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
}
fun updateDate() {
binding.date.text = dateUtil.dateAndTimeString(overviewData.fromTime)
binding.zoom.text = rangeToDisplay.toString()
}
@Suppress("UNUSED_PARAMETER")
@SuppressLint("SetTextI18n")
fun updateGUI(from: String) {
aapsLogger.debug(LTag.UI, "updateGui $from")
updateDate()
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

@ -14,11 +14,13 @@ import info.nightscout.androidaps.danaRKorean.DanaRKoreanPlugin
import info.nightscout.androidaps.danaRv2.DanaRv2Plugin
import info.nightscout.androidaps.danar.DanaRPlugin
import info.nightscout.androidaps.danars.DanaRSPlugin
import info.nightscout.androidaps.diaconn.DiaconnG8Plugin
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.events.EventRebuildTabs
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.plugin.general.openhumans.OpenHumansUploader
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin
import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin
@ -29,7 +31,6 @@ import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin
import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin
import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus
import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
import info.nightscout.androidaps.plugins.general.tidepool.TidepoolPlugin
import info.nightscout.androidaps.plugins.general.wear.WearPlugin
@ -99,6 +100,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
@Inject lateinit var passwordCheck: PasswordCheck
@Inject lateinit var nsSettingStatus: NSSettingsStatus
@Inject lateinit var openHumansUploader: OpenHumansUploader
@Inject lateinit var diaconnG8Plugin: DiaconnG8Plugin
override fun onAttach(context: Context) {
AndroidSupportInjection.inject(this)
@ -173,6 +175,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
addPreferencesFromResourceIfEnabled(localInsightPlugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(comboPlugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(medtronicPumpPlugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(diaconnG8Plugin, rootKey, config.PUMPDRIVERS)
addPreferencesFromResource(R.xml.pref_pump, rootKey, config.PUMPDRIVERS)
addPreferencesFromResourceIfEnabled(virtualPumpPlugin, rootKey)
addPreferencesFromResourceIfEnabled(insulinOrefFreePeakPlugin, rootKey)
@ -182,7 +185,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
addPreferencesFromResourceIfEnabled(automationPlugin, rootKey)
addPreferencesFromResourceIfEnabled(wearPlugin, rootKey)
addPreferencesFromResourceIfEnabled(statusLinePlugin, rootKey)
addPreferencesFromResource(R.xml.pref_alerts, rootKey) // TODO not organized well
addPreferencesFromResource(R.xml.pref_alerts, rootKey)
addPreferencesFromResource(R.xml.pref_datachoices, rootKey)
addPreferencesFromResourceIfEnabled(maintenancePlugin, rootKey)
addPreferencesFromResourceIfEnabled(openHumansUploader, rootKey)

View file

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

View file

@ -19,7 +19,7 @@ class SingleFragmentActivity : DaggerAppCompatActivityWithResult() {
private var plugin: PluginBase? = null
public override fun onCreate(savedInstanceState: Bundle?) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_single_fragment)
plugin = pluginStore.plugins[intent.getIntExtra("plugin", -1)]
@ -52,7 +52,7 @@ class SingleFragmentActivity : DaggerAppCompatActivityWithResult() {
return super.onCreateOptionsMenu(menu)
}
public override fun attachBaseContext(newBase: Context) {
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(LocaleHelper.wrap(newBase))
}
}

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.activities
import android.annotation.SuppressLint
import android.os.Bundle
import info.nightscout.androidaps.R
import info.nightscout.androidaps.database.entities.UserEntry.Action
@ -21,14 +22,28 @@ class StatsActivity : NoSplashAppCompatActivity() {
private lateinit var binding: ActivityStatsBinding
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityStatsBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tdds.text = tddCalculator.stats()
binding.tir.text = tirCalculator.stats()
binding.activity.text = activityMonitor.stats()
binding.tdds.text = getString(R.string.tdd) + ": " + getString(R.string.calculation_in_progress)
binding.tir.text = getString(R.string.tir) + ": " + getString(R.string.calculation_in_progress)
binding.activity.text = getString(R.string.activitymonitor) + ": " + getString(R.string.calculation_in_progress)
Thread {
val tdds = tddCalculator.stats()
runOnUiThread { binding.tdds.text = tdds }
}.start()
Thread {
val tir = tirCalculator.stats()
runOnUiThread { binding.tir.text = tir }
}.start()
Thread {
val activity = activityMonitor.stats()
runOnUiThread { binding.activity.text = activity }
}.start()
binding.ok.setOnClickListener { finish() }
binding.reset.setOnClickListener {

View file

@ -31,7 +31,7 @@ import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.plugins.treatments.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T

View file

@ -24,7 +24,7 @@ import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.plugins.treatments.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.activities.fragments.TreatmentsCareportalFragment.RecyclerViewAdapter.TherapyEventsViewHolder
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy

View file

@ -29,7 +29,7 @@ import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientR
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged
import info.nightscout.androidaps.plugins.treatments.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.activities.fragments.TreatmentsProfileSwitchFragment.RecyclerProfileViewAdapter.ProfileSwitchViewHolder
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy

View file

@ -28,7 +28,7 @@ import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.plugins.treatments.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.activities.fragments.TreatmentsTempTargetFragment.RecyclerViewAdapter.TempTargetsViewHolder
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy

View file

@ -190,7 +190,6 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() {
override fun getItemCount(): Int = tempBasalList.size
@Deprecated("remove remove functionality after finish")
inner class TempBasalsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val binding = TreatmentsTempbasalsItemBinding.bind(itemView)

View file

@ -19,7 +19,7 @@ import info.nightscout.androidaps.interfaces.ImportExportPrefs
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.treatments.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T

View file

@ -11,6 +11,7 @@ import org.json.JSONObject
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.abs
@Singleton
class DefaultProfile @Inject constructor(val dateUtil: DateUtil) {
@ -120,12 +121,12 @@ class DefaultProfile @Inject constructor(val dateUtil: DateUtil) {
val high = map.ceilingEntry(key)
var res: Array<Double>? = null
if (low != null && high != null) {
res = if (Math.abs(key - low.key) < Math.abs(key - high.key))
res = if (abs(key - low.key) < abs(key - high.key))
low.value
else
high.value
} else if (low != null || high != null) {
res = if (low != null) low.value else high.value
res = if (low != null) low.value else high!!.value
}
return res
}

View file

@ -82,5 +82,9 @@ class CompatDBHelper @Inject constructor(
aapsLogger.debug(LTag.DATABASE, "Firing EventProfileSwitchChanged")
rxBus.send(EventProfileSwitchChanged())
}
it.filterIsInstance<OfflineEvent>().firstOrNull()?.let {
aapsLogger.debug(LTag.DATABASE, "Firing EventOfflineChange")
rxBus.send(EventOfflineChange())
}
}
}

View file

@ -1,296 +0,0 @@
package info.nightscout.androidaps.db;
import android.content.Context;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.dao.CloseableIterator;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.stmt.DeleteBuilder;
import com.j256.ormlite.stmt.PreparedQuery;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.stmt.Where;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import info.nightscout.androidaps.events.EventRefreshOverview;
import info.nightscout.androidaps.interfaces.ActivePlugin;
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.openhumans.OpenHumansUploader;
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin;
import info.nightscout.androidaps.utils.DateUtil;
/**
* This Helper contains all resource to provide a central DB management functionality. Only methods handling
* data-structure (and not the DB content) should be contained in here (meaning DDL and not SQL).
* <p>
* This class can safely be called from Services, but should not call Services to avoid circular dependencies.
* One major issue with this (right now) are the scheduled events, which are put into the service. Therefor all
* direct calls to the corresponding methods (eg. resetDatabases) should be done by a central service.
*/
public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
@Inject AAPSLogger aapsLogger;
@Inject RxBusWrapper rxBus;
@Inject VirtualPumpPlugin virtualPumpPlugin;
@Inject OpenHumansUploader openHumansUploader;
@Inject ActivePlugin activePlugin;
@Inject DateUtil dateUtil;
public static final String DATABASE_NAME = "AndroidAPSDb";
private static final int DATABASE_VERSION = 13;
public static Long earliestDataChange = null;
private int oldVersion = 0;
private int newVersion = 0;
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
StaticInjector.Companion.getInstance().androidInjector().inject(this);
onCreate(getWritableDatabase(), getConnectionSource());
//onUpgrade(getWritableDatabase(), getConnectionSource(), 1,1);
}
@Override
public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) {
try {
aapsLogger.info(LTag.DATABASE, "onCreate");
TableUtils.createTableIfNotExists(connectionSource, OmnipodHistoryRecord.class);
TableUtils.createTableIfNotExists(connectionSource, OHQueueItem.class);
} catch (SQLException e) {
aapsLogger.error("Can't create database", e);
throw new RuntimeException(e);
}
}
@Override
public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) {
try {
this.oldVersion = oldVersion;
this.newVersion = newVersion;
if (oldVersion < 7) {
aapsLogger.info(LTag.DATABASE, "onUpgrade");
onCreate(database, connectionSource);
}
TableUtils.createTableIfNotExists(connectionSource, OHQueueItem.class);
} catch (SQLException e) {
aapsLogger.error("Can't drop databases", e);
throw new RuntimeException(e);
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
aapsLogger.info(LTag.DATABASE, "Do nothing for downgrading...");
aapsLogger.info(LTag.DATABASE, "oldVersion: {}, newVersion: {}", oldVersion, newVersion);
}
public int getOldVersion() {
return oldVersion;
}
public int getNewVersion() {
return newVersion;
}
public long size(String database) {
return DatabaseUtils.queryNumEntries(getReadableDatabase(), database);
}
// --------------------- DB resets ---------------------
public void resetDatabases() {
try {
TableUtils.dropTable(connectionSource, OmnipodHistoryRecord.class, true);
TableUtils.createTableIfNotExists(connectionSource, OmnipodHistoryRecord.class);
updateEarliestDataChange(0);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
virtualPumpPlugin.setFakingStatus(true);
new java.util.Timer().schedule(
new java.util.TimerTask() {
@Override
public void run() {
rxBus.send(new EventRefreshOverview("resetDatabases", false));
}
},
3000
);
}
// ------------------ getDao -------------------------------------------
private Dao<OmnipodHistoryRecord, Long> getDaoPodHistory() throws SQLException {
return getDao(OmnipodHistoryRecord.class);
}
private Dao<OHQueueItem, Long> getDaoOpenHumansQueue() throws SQLException {
return getDao(OHQueueItem.class);
}
public static void updateEarliestDataChange(long newDate) {
if (earliestDataChange == null) {
earliestDataChange = newDate;
return;
}
if (newDate < earliestDataChange) {
earliestDataChange = newDate;
}
}
// ---------------- Food handling ---------------
// ---------------- PodHistory handling ---------------
public void createOrUpdate(OmnipodHistoryRecord omnipodHistoryRecord) {
try {
getDaoPodHistory().createOrUpdate(omnipodHistoryRecord);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public List<OmnipodHistoryRecord> getAllOmnipodHistoryRecordsFromTimeStamp(long from, boolean ascending) {
try {
Dao<OmnipodHistoryRecord, Long> daoPodHistory = getDaoPodHistory();
List<OmnipodHistoryRecord> podHistories;
QueryBuilder<OmnipodHistoryRecord, Long> queryBuilder = daoPodHistory.queryBuilder();
queryBuilder.orderBy("date", ascending);
//queryBuilder.limit(100L);
Where where = queryBuilder.where();
where.ge("date", from);
PreparedQuery<OmnipodHistoryRecord> preparedQuery = queryBuilder.prepare();
podHistories = daoPodHistory.query(preparedQuery);
return podHistories;
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return new ArrayList<>();
}
public OmnipodHistoryRecord findOmnipodHistoryRecordByPumpId(long pumpId) {
try {
Dao<OmnipodHistoryRecord, Long> daoPodHistory = getDaoPodHistory();
QueryBuilder<OmnipodHistoryRecord, Long> queryBuilder = daoPodHistory.queryBuilder();
queryBuilder.orderBy("date", false);
Where<OmnipodHistoryRecord, Long> where = queryBuilder.where();
where.eq("pumpId", pumpId);
PreparedQuery<OmnipodHistoryRecord> preparedQuery = queryBuilder.prepare();
return daoPodHistory.queryForFirst(preparedQuery);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return null;
}
/*
TODO implement again for database branch // Copied from xDrip+
String calculateDirection(BgReading bgReading) {
// Rework to get bgreaings from internal DB and calculate on that base
List<BgReading> bgReadingsList = MainApp.getDbHelper().getAllBgreadingsDataFromTime(bgReading.date - T.mins(10).msecs(), false);
if (bgReadingsList == null || bgReadingsList.size() < 2)
return "NONE";
BgReading current = bgReadingsList.get(1);
BgReading previous = bgReadingsList.get(0);
if (bgReadingsList.get(1).date < bgReadingsList.get(0).date) {
current = bgReadingsList.get(0);
previous = bgReadingsList.get(1);
}
double slope;
// Avoid division by 0
if (current.date == previous.date)
slope = 0;
else
slope = (previous.value - current.value) / (previous.date - current.date);
// aapsLogger.error(LTag.GLUCOSE, "Slope is :" + slope + " delta " + (previous.value - current.value) + " date difference " + (current.date - previous.date));
double slope_by_minute = slope * 60000;
String arrow = "NONE";
if (slope_by_minute <= (-3.5)) {
arrow = "DoubleDown";
} else if (slope_by_minute <= (-2)) {
arrow = "SingleDown";
} else if (slope_by_minute <= (-1)) {
arrow = "FortyFiveDown";
} else if (slope_by_minute <= (1)) {
arrow = "Flat";
} else if (slope_by_minute <= (2)) {
arrow = "FortyFiveUp";
} else if (slope_by_minute <= (3.5)) {
arrow = "SingleUp";
} else if (slope_by_minute <= (40)) {
arrow = "DoubleUp";
}
// aapsLogger.error(LTag.GLUCOSE, "Direction set to: " + arrow);
return arrow;
}
*/
// ---------------- Open Humans Queue handling ---------------
public void clearOpenHumansQueue() {
try {
TableUtils.clearTable(connectionSource, OHQueueItem.class);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public void createOrUpdate(OHQueueItem item) {
try {
getDaoOpenHumansQueue().createOrUpdate(item);
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public void removeAllOHQueueItemsWithIdSmallerThan(long id) {
try {
DeleteBuilder<OHQueueItem, Long> deleteBuilder = getDaoOpenHumansQueue().deleteBuilder();
deleteBuilder.where().le("id", id);
deleteBuilder.delete();
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
}
public List<OHQueueItem> getAllOHQueueItems(Long maxEntries) {
try {
return getDaoOpenHumansQueue()
.queryBuilder()
.orderBy("id", true)
.limit(maxEntries)
.query();
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return Collections.emptyList();
}
public long getOHQueueSize() {
try {
return getDaoOpenHumansQueue().countOf();
} catch (SQLException e) {
aapsLogger.error("Unhandled exception", e);
}
return 0L;
}
}

View file

@ -1,84 +0,0 @@
package info.nightscout.androidaps.db;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.j256.ormlite.dao.CloseableIterator;
import java.sql.SQLException;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.interfaces.DatabaseHelperInterface;
@Deprecated
@Singleton
public class DatabaseHelperProvider implements DatabaseHelperInterface {
@Inject DatabaseHelperProvider() {
}
@Override public void createOrUpdate(@NonNull OmnipodHistoryRecord record) {
MainApp.Companion.getDbHelper().createOrUpdate(record);
}
@Override public boolean createOrUpdate(@NonNull TemporaryBasal tempBasal) {
// return MainApp.Companion.getDbHelper().createOrUpdate(tempBasal);
return false;
}
@Nullable @Override public TemporaryBasal findTempBasalByPumpId(long id) {
// return MainApp.Companion.getDbHelper().findTempBasalByPumpId(id);
return null;
}
@Deprecated
@NonNull @Override public List<TemporaryBasal> getTemporaryBasalsDataFromTime(long mills, boolean ascending) {
// return MainApp.Companion.getDbHelper().getTemporaryBasalsDataFromTime(mills, ascending);
return null;
}
@NonNull @Override public List<OmnipodHistoryRecord> getAllOmnipodHistoryRecordsFromTimestamp(long timestamp, boolean ascending) {
return MainApp.Companion.getDbHelper().getAllOmnipodHistoryRecordsFromTimeStamp(timestamp, ascending);
}
@Nullable @Override public OmnipodHistoryRecord findOmnipodHistoryRecordByPumpId(long pumpId) {
return MainApp.Companion.getDbHelper().findOmnipodHistoryRecordByPumpId(pumpId);
}
@Override public void delete(@NonNull ExtendedBolus extendedBolus) {
// MainApp.Companion.getDbHelper().delete(extendedBolus);
}
@Nullable @Override public ExtendedBolus getExtendedBolusByPumpId(long pumpId) {
// return MainApp.Companion.getDbHelper().getExtendedBolusByPumpId(pumpId);
return null;
}
@Override public void resetDatabases() {
MainApp.Companion.getDbHelper().resetDatabases();
}
@Override public void createOrUpdate(@NonNull OHQueueItem record) {
MainApp.Companion.getDbHelper().createOrUpdate(record);
}
@NonNull @Override public List<OHQueueItem> getAllOHQueueItems(long maxEntries) {
return MainApp.Companion.getDbHelper().getAllOHQueueItems(maxEntries);
}
@Override public long getOHQueueSize() {
return MainApp.Companion.getDbHelper().getOHQueueSize();
}
@Override public void clearOpenHumansQueue() {
MainApp.Companion.getDbHelper().clearOpenHumansQueue();
}
@Override public void removeAllOHQueueItemsWithIdSmallerThan(long id) {
MainApp.Companion.getDbHelper().removeAllOHQueueItemsWithIdSmallerThan(id);
}
}

View file

@ -1,2 +0,0 @@
package info.nightscout.androidaps.db

View file

@ -4,9 +4,8 @@ import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.MainActivity
import info.nightscout.androidaps.activities.*
import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity
import info.nightscout.androidaps.activities.HistoryBrowseActivity
import info.nightscout.androidaps.plugins.general.maintenance.activities.LogSettingActivity
import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansLoginActivity
import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity
import info.nightscout.androidaps.plugins.general.smsCommunicator.activities.SmsCommunicatorOtpActivity
import info.nightscout.androidaps.setupwizard.SetupWizardActivity
@ -28,6 +27,5 @@ abstract class ActivitiesModule {
@ContributesAndroidInjector abstract fun contributesStatsActivity(): StatsActivity
@ContributesAndroidInjector abstract fun contributesSurveyActivity(): SurveyActivity
@ContributesAndroidInjector abstract fun contributesDefaultProfileActivity(): ProfileHelperActivity
@ContributesAndroidInjector abstract fun contributesOpenHumansLoginActivity(): OpenHumansLoginActivity
}

View file

@ -8,15 +8,19 @@ import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.automation.di.AutomationModule
import info.nightscout.androidaps.combo.di.ComboModule
import info.nightscout.androidaps.dana.di.DanaHistoryModule
import info.nightscout.androidaps.di.CoreModule
import info.nightscout.androidaps.dana.di.DanaModule
import info.nightscout.androidaps.danar.di.DanaRModule
import info.nightscout.androidaps.danars.di.DanaRSModule
import info.nightscout.androidaps.database.DatabaseModule
import info.nightscout.androidaps.di.CoreModule
import info.nightscout.androidaps.diaconn.di.DiaconnG8Module
import info.nightscout.androidaps.insight.di.InsightDatabaseModule
import info.nightscout.androidaps.insight.di.InsightModule
import info.nightscout.androidaps.plugin.general.openhumans.dagger.OpenHumansModule
import info.nightscout.androidaps.plugins.pump.common.di.PumpCommonModule
import info.nightscout.androidaps.plugins.pump.common.di.RileyLinkModule
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.eros.dagger.OmnipodErosModule
import javax.inject.Singleton
@ -36,8 +40,10 @@ import javax.inject.Singleton
CommandQueueModule::class,
ObjectivesModule::class,
WizardModule::class,
PumpCommonModule::class,
RileyLinkModule::class,
MedtronicModule::class,
OmnipodDashModule::class,
OmnipodErosModule::class,
APSModule::class,
PreferencesModule::class,
@ -54,7 +60,8 @@ import javax.inject.Singleton
InsightModule::class,
InsightDatabaseModule::class,
WorkersModule::class,
OHUploaderModule::class
DiaconnG8Module::class,
OpenHumansModule::class
]
)
interface AppComponent : AndroidInjector<MainApp> {

View file

@ -8,22 +8,25 @@ import dagger.Provides
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.db.DatabaseHelperProvider
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger
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.PluginStore
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctionImplementation
import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefsImpl
import info.nightscout.androidaps.plugins.general.maintenance.PrefFileListProvider
import info.nightscout.androidaps.plugins.general.nsclient.DataSyncSelectorImplementation
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
import info.nightscout.androidaps.plugins.pump.PumpSyncImplementation
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import info.nightscout.androidaps.queue.CommandQueue
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.androidNotification.NotificationHolderImpl
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.buildHelper.BuildHelperImpl
import info.nightscout.androidaps.utils.buildHelper.ConfigImpl
import info.nightscout.androidaps.utils.resources.IconsProviderImplementation
import info.nightscout.androidaps.utils.resources.ResourceHelper
@ -56,9 +59,11 @@ open class AppModule {
@Provides
@Singleton
fun provideStorage(): Storage {
return FileStorage()
}
fun provideStorage(): Storage = FileStorage()
@Provides
@Singleton
fun provideBuildHelper(config: Config, fileListProvider: PrefFileListProvider): BuildHelper = BuildHelperImpl(config, fileListProvider)
@Provides
@Singleton
@ -66,29 +71,35 @@ open class AppModule {
@Provides
@Singleton
fun provideProfileFunction(aapsLogger: AAPSLogger, sp: SP, resourceHelper: ResourceHelper, activePlugin: ActivePlugin, repository: AppRepository, dateUtil: DateUtil): ProfileFunction {
return ProfileFunctionImplementation(aapsLogger, sp, resourceHelper, activePlugin, repository, dateUtil)
}
fun provideProfileFunction(
aapsLogger: AAPSLogger, sp: SP, rxBus: RxBusWrapper, resourceHelper:
ResourceHelper, activePlugin:
ActivePlugin, repository: AppRepository, dateUtil: DateUtil, config: Config, hardLimits: HardLimits
): ProfileFunction =
ProfileFunctionImplementation(
aapsLogger, sp, rxBus, resourceHelper, activePlugin, repository, dateUtil,
config, hardLimits
)
@Module
interface AppBindings {
@Binds fun bindContext(mainApp: MainApp): Context
@Binds fun bindInjector(mainApp: MainApp): HasAndroidInjector
@Binds fun bindActivePluginProvider(pluginStore: PluginStore): ActivePlugin
@Binds fun bindCommandQueueProvider(commandQueue: CommandQueue): CommandQueueProvider
@Binds fun bindConfigInterface(config: ConfigImpl): Config
@Binds fun bindConfigBuilderInterface(configBuilderPlugin: ConfigBuilderPlugin): ConfigBuilder
@Binds fun bindTreatmentsInterface(treatmentsPlugin: TreatmentsPlugin): TreatmentsInterface
@Binds fun bindDatabaseHelperInterface(databaseHelperProvider: DatabaseHelperProvider): DatabaseHelperInterface
@Binds fun bindNotificationHolderInterface(notificationHolder: NotificationHolderImpl): NotificationHolder
@Binds fun bindImportExportPrefsInterface(importExportPrefs: ImportExportPrefsImpl): ImportExportPrefs
@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 bindSmsCommunicatorInterface(smsCommunicatorPlugin: SmsCommunicatorPlugin): SmsCommunicator
@Binds fun bindDataSyncSelector(dataSyncSelectorImplementation: DataSyncSelectorImplementation): DataSyncSelector
@Binds fun bindPumpSync(pumpSyncImplementation: PumpSyncImplementation): PumpSync
@Binds fun bindPumpSync(pumpSyncImplementation: PumpSyncImplementation): PumpSync
}
}

View file

@ -2,9 +2,7 @@ package info.nightscout.androidaps.dependencyInjection
import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.db.DatabaseHelper
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus
import info.nightscout.androidaps.plugins.treatments.TreatmentService
import info.nightscout.androidaps.utils.wizard.BolusWizard
import info.nightscout.androidaps.utils.wizard.QuickWizardEntry
@ -14,9 +12,6 @@ abstract class DataClassesModule {
@ContributesAndroidInjector abstract fun glucoseStatusInjector(): GlucoseStatus
@ContributesAndroidInjector abstract fun databaseHelperInjector(): DatabaseHelper
@ContributesAndroidInjector abstract fun treatmentServiceInjector(): TreatmentService
@ContributesAndroidInjector abstract fun bolusWizardInjector(): BolusWizard
@ContributesAndroidInjector abstract fun quickWizardEntryInjector(): QuickWizardEntry
}

View file

@ -20,8 +20,6 @@ import info.nightscout.androidaps.plugins.general.automation.dialogs.EditTrigger
import info.nightscout.androidaps.plugins.general.food.FoodFragment
import info.nightscout.androidaps.plugins.general.maintenance.MaintenanceFragment
import info.nightscout.androidaps.plugins.general.nsclient.NSClientFragment
import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansFragment
import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansLoginActivity
import info.nightscout.androidaps.plugins.general.overview.OverviewFragment
import info.nightscout.androidaps.plugins.general.overview.dialogs.EditQuickWizardDialog
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorFragment
@ -70,8 +68,6 @@ abstract class FragmentsModule {
@ContributesAndroidInjector abstract fun contributesVirtualPumpFragment(): VirtualPumpFragment
@ContributesAndroidInjector abstract fun contributesOpenHumansFragment(): OpenHumansFragment
@ContributesAndroidInjector abstract fun contributesCalibrationDialog(): CalibrationDialog
@ContributesAndroidInjector abstract fun contributesCarbsDialog(): CarbsDialog
@ContributesAndroidInjector abstract fun contributesCareDialog(): CareDialog
@ -95,7 +91,5 @@ abstract class FragmentsModule {
@ContributesAndroidInjector abstract fun contributesWizardDialog(): WizardDialog
@ContributesAndroidInjector abstract fun contributesWizardInfoDialog(): WizardInfoDialog
@ContributesAndroidInjector abstract fun contributesExchangeAuthTokenDialog(): OpenHumansLoginActivity.ExchangeAuthTokenDialog
@ContributesAndroidInjector abstract fun contributesPasswordCheck(): PasswordCheck
}

View file

@ -1,12 +0,0 @@
package info.nightscout.androidaps.dependencyInjection
import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.plugins.general.openhumans.OHUploadWorker
@Module
@Suppress("unused")
abstract class OHUploaderModule {
@ContributesAndroidInjector abstract fun contributesOHUploadWorkerInjector(): OHUploadWorker
}

View file

@ -17,7 +17,6 @@ abstract class ObjectivesModule {
@ContributesAndroidInjector abstract fun objective5Injector(): Objective5
@ContributesAndroidInjector abstract fun objective6Injector(): Objective6
@ContributesAndroidInjector abstract fun objective7Injector(): Objective7
@ContributesAndroidInjector abstract fun objective8Injector(): Objective8
@ContributesAndroidInjector abstract fun objective9Injector(): Objective9
@ContributesAndroidInjector abstract fun objective10Injector(): Objective10

View file

@ -8,7 +8,9 @@ import info.nightscout.androidaps.danaRKorean.DanaRKoreanPlugin
import info.nightscout.androidaps.danaRv2.DanaRv2Plugin
import info.nightscout.androidaps.danar.DanaRPlugin
import info.nightscout.androidaps.danars.DanaRSPlugin
import info.nightscout.androidaps.diaconn.DiaconnG8Plugin
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.plugin.general.openhumans.OpenHumansUploader
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin
import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin
@ -25,7 +27,6 @@ import info.nightscout.androidaps.plugins.general.dataBroadcaster.DataBroadcastP
import info.nightscout.androidaps.plugins.general.food.FoodPlugin
import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin
import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin
import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader
import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin
import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
@ -41,13 +42,11 @@ import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin
import info.nightscout.androidaps.plugins.pump.mdi.MDIPlugin
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin
import info.nightscout.androidaps.plugins.pump.omnipod.eros.OmnipodErosPumpPlugin
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
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.plugins.source.*
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import javax.inject.Qualifier
@Module
@ -149,11 +148,11 @@ abstract class PluginsModule {
@IntKey(140)
abstract fun bindComboPlugin(plugin: ComboPlugin): PluginBase
// @Binds
// @PumpDriver
// @IntoMap
// @IntKey(150)
// abstract fun bindMedtronicPumpPlugin(plugin: MedtronicPumpPlugin): PluginBase
@Binds
@PumpDriver
@IntoMap
@IntKey(150)
abstract fun bindMedtronicPumpPlugin(plugin: MedtronicPumpPlugin): PluginBase
// @Binds
// @PumpDriver
@ -161,6 +160,12 @@ abstract class PluginsModule {
// @IntKey(155)
// abstract fun bindOmnipodPumpPlugin(plugin: OmnipodErosPumpPlugin): PluginBase
@Binds
@PumpDriver
@IntoMap
@IntKey(155)
abstract fun bindDiaconnG8Plugin(plugin: DiaconnG8Plugin): PluginBase
@Binds
@NotNSClient
@IntoMap
@ -203,12 +208,6 @@ abstract class PluginsModule {
@IntKey(250)
abstract fun bindAutomationPlugin(plugin: AutomationPlugin): PluginBase
@Binds
@AllConfigs
@IntoMap
@IntKey(260)
abstract fun bindTreatmentsPlugin(plugin: TreatmentsPlugin): PluginBase
@Binds
@AllConfigs
@IntoMap
@ -347,6 +346,12 @@ abstract class PluginsModule {
// @IntKey(480)
// abstract fun bindOpenHumansPlugin(plugin: OpenHumansUploader): PluginBase
@Binds
@NotNSClient
@IntoMap
@IntKey(480)
abstract fun bindsOpenHumansPlugin(plugin: OpenHumansUploader): PluginBase
@Binds
@AllConfigs
@IntoMap

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

View file

@ -191,7 +191,7 @@ class FillDialog : DialogFragmentWithDate() {
commandQueue.bolus(detailedBolusInfo, object : Callback() {
override fun run() {
if (!result.success) {
ErrorHelperActivity.runAlarm(ctx, result.comment, resourceHelper.gs(R.string.treatmentdeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror)
ErrorHelperActivity.runAlarm(ctx, result.comment, resourceHelper.gs(R.string.treatmentdeliveryerror), R.raw.boluserror)
}
}
})

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.UserEntry.Action
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.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger
@ -186,7 +186,7 @@ class InsulinDialog : DialogFragmentWithDate() {
ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.EATING_SOON),
ValueWithUnit.fromGlucoseUnit(eatingSoonTT, units.asText),
ValueWithUnit.Minute(eatingSoonTTDuration))
disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction(
disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = System.currentTimeMillis(),
duration = TimeUnit.MINUTES.toMillis(eatingSoonTTDuration.toLong()),
reason = TemporaryTarget.Reason.EATING_SOON,
@ -224,7 +224,7 @@ class InsulinDialog : DialogFragmentWithDate() {
commandQueue.bolus(detailedBolusInfo, object : Callback() {
override fun run() {
if (!result.success) {
ErrorHelperActivity.runAlarm(ctx, result.comment, resourceHelper.gs(R.string.treatmentdeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror)
ErrorHelperActivity.runAlarm(ctx, result.comment, resourceHelper.gs(R.string.treatmentdeliveryerror), R.raw.boluserror)
}
}
})

View file

@ -3,6 +3,7 @@ package info.nightscout.androidaps.dialogs
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -12,9 +13,13 @@ import androidx.fragment.app.FragmentManager
import dagger.android.support.DaggerDialogFragment
import info.nightscout.androidaps.R
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.UserEntry.Action
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.events.EventPreferenceChange
import info.nightscout.androidaps.events.EventRefreshOverview
@ -29,8 +34,14 @@ import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import javax.inject.Inject
class LoopDialog : DaggerDialogFragment() {
@ -48,19 +59,27 @@ class LoopDialog : DaggerDialogFragment() {
@Inject lateinit var commandQueue: CommandQueueProvider
@Inject lateinit var configBuilder: ConfigBuilder
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var repository: AppRepository
@Inject lateinit var objectivePlugin: ObjectivesPlugin
private var showOkCancel: Boolean = true
private var _binding: DialogLoopBinding? = null
private var loopHandler = Handler()
private var refreshDialog: Runnable? = null
private var loopHandler = Handler(Looper.getMainLooper())
private lateinit var refreshDialog: Runnable
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
val disposable = CompositeDisposable()
override fun 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
)
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
@ -118,6 +137,7 @@ class LoopDialog : DaggerDialogFragment() {
super.onDestroyView()
_binding = null
loopHandler.removeCallbacksAndMessages(null)
disposable.clear()
}
var task: Runnable? = null
@ -141,21 +161,74 @@ class LoopDialog : DaggerDialogFragment() {
aapsLogger.debug("UpdateGUI from $from")
val pumpDescription: PumpDescription = activePlugin.activePump.pumpDescription
val closedLoopAllowed = constraintChecker.isClosedLoopAllowed(Constraint(true))
val closedLoopAllowed2 = objectivePlugin.objectives[ObjectivesPlugin.MAXIOB_OBJECTIVE].isCompleted
val lgsEnabled = constraintChecker.isLgsAllowed(Constraint(true))
val apsMode = sp.getString(R.string.key_aps_mode, "open")
if (profileFunction.isProfileValid("LoopDialogUpdateGUI")) {
if (loopPlugin.isEnabled(PluginType.LOOP)) {
val pump = activePlugin.activePump
binding.overviewDisconnect15m.visibility = pumpDescription.tempDurationStep15mAllowed.toVisibility()
binding.overviewDisconnect30m.visibility = pumpDescription.tempDurationStep30mAllowed.toVisibility()
when {
pump.isSuspended() -> {
binding.overviewLoop.visibility = View.GONE
binding.overviewSuspend.visibility = View.GONE
binding.overviewPump.visibility = View.GONE
}
!profileFunction.isProfileValid("LoopDialogUpdateGUI") -> {
binding.overviewLoop.visibility = View.GONE
binding.overviewSuspend.visibility = View.GONE
binding.overviewPump.visibility = View.GONE
}
loopPlugin.isDisconnected -> {
binding.overviewLoop.visibility = View.GONE
binding.overviewSuspend.visibility = View.GONE
binding.overviewPump.visibility = View.VISIBLE
binding.overviewPumpHeader.text = resourceHelper.gs(R.string.reconnect)
binding.overviewDisconnectButtons.visibility = View.VISIBLE
binding.overviewReconnect.visibility = View.VISIBLE
}
!loopPlugin.isEnabled(PluginType.LOOP) -> {
binding.overviewLoop.visibility = View.VISIBLE
binding.overviewEnable.visibility = View.VISIBLE
binding.overviewDisable.visibility = View.GONE
binding.overviewSuspend.visibility = View.GONE
binding.overviewPump.visibility = View.VISIBLE
binding.overviewReconnect.visibility = View.GONE
}
loopPlugin.isSuspended -> {
binding.overviewLoop.visibility = View.GONE
binding.overviewSuspend.visibility = View.VISIBLE
binding.overviewSuspendHeader.text = resourceHelper.gs(R.string.resumeloop)
binding.overviewSuspendButtons.visibility = View.VISIBLE
binding.overviewResume.visibility = View.VISIBLE
binding.overviewPump.visibility = View.GONE
binding.overviewReconnect.visibility = View.GONE
}
else -> {
binding.overviewLoop.visibility = View.VISIBLE
binding.overviewEnable.visibility = View.GONE
when {
closedLoopAllowed.value() -> {
binding.overviewCloseloop.visibility = (apsMode != "closed").toVisibility()
binding.overviewLgsloop.visibility = (apsMode != "lgs").toVisibility()
binding.overviewOpenloop.visibility = (apsMode != "open").toVisibility()
apsMode == "closed" -> {
binding.overviewCloseloop.visibility = View.GONE
binding.overviewLgsloop.visibility = View.VISIBLE
binding.overviewOpenloop.visibility = View.VISIBLE
}
lgsEnabled.value() -> {
binding.overviewCloseloop.visibility = View.GONE
binding.overviewLgsloop.visibility = (apsMode != "lgs").toVisibility()
binding.overviewOpenloop.visibility = (apsMode != "open").toVisibility()
apsMode == "lgs" -> {
binding.overviewCloseloop.visibility = closedLoopAllowed.value().toVisibility() //show Close loop button only if Close loop allowed
binding.overviewLgsloop.visibility = View.GONE
binding.overviewOpenloop.visibility = View.VISIBLE
}
apsMode == "open" -> {
binding.overviewCloseloop.visibility = closedLoopAllowed2.toVisibility() //show CloseLoop button only if Objective 6 is completed (closedLoopAllowed always false in open loop mode)
binding.overviewLgsloop.visibility = lgsEnabled.value().toVisibility()
binding.overviewOpenloop.visibility = View.GONE
}
else -> {
@ -164,70 +237,39 @@ class LoopDialog : DaggerDialogFragment() {
binding.overviewOpenloop.visibility = View.GONE
}
}
binding.overviewEnable.visibility = View.GONE
binding.overviewDisable.visibility = View.VISIBLE
if (!loopPlugin.isSuspended) {
binding.overviewSuspendHeader.text = resourceHelper.gs(R.string.suspendloop)
binding.overviewResume.visibility = View.GONE
binding.overviewSuspendButtons.visibility = View.VISIBLE
binding.overviewSuspend.visibility = View.VISIBLE
} else {
if (!loopPlugin.isDisconnected) {
binding.overviewSuspendHeader.text = resourceHelper.gs(R.string.resumeloop)
binding.overviewResume.visibility = View.VISIBLE
binding.overviewSuspendButtons.visibility = View.GONE
binding.overviewSuspend.visibility = View.VISIBLE
} else
binding.overviewSuspend.visibility = View.GONE
}
} else {
binding.overviewEnable.visibility = View.VISIBLE
binding.overviewDisable.visibility = View.GONE
binding.overviewSuspend.visibility = View.GONE
}
if (!loopPlugin.isDisconnected) {
binding.overviewSuspend.visibility = View.VISIBLE
binding.overviewSuspendHeader.text = resourceHelper.gs(R.string.suspendloop)
binding.overviewSuspendButtons.visibility = View.VISIBLE
binding.overviewResume.visibility = View.GONE
binding.overviewPump.visibility = View.VISIBLE
binding.overviewPumpHeader.text = resourceHelper.gs(R.string.disconnectpump)
binding.overviewDisconnect15m.visibility = pumpDescription.tempDurationStep15mAllowed.toVisibility()
binding.overviewDisconnect30m.visibility = pumpDescription.tempDurationStep30mAllowed.toVisibility()
binding.overviewDisconnectButtons.visibility = View.VISIBLE
binding.overviewReconnect.visibility = View.GONE
} else {
binding.overviewPumpHeader.text = resourceHelper.gs(R.string.reconnect)
binding.overviewDisconnectButtons.visibility = View.GONE
binding.overviewReconnect.visibility = View.VISIBLE
}
binding.overviewLoop.visibility = (!loopPlugin.isSuspended && !loopPlugin.isDisconnected).toVisibility()
}
val profile = profileFunction.getProfile()
val profileStore = activePlugin.activeProfileSource.profile
if (profile == null || profileStore == null) {
ToastUtils.showToastInUiThread(ctx, resourceHelper.gs(R.string.noprofile))
dismiss()
return
}
}
private fun onClickOkCancelEnabled(v: View): Boolean {
var description = ""
when (v.id) {
R.id.overview_closeloop -> description = resourceHelper.gs(R.string.closedloop)
R.id.overview_lgsloop -> description = resourceHelper.gs(R.string.lowglucosesuspend)
R.id.overview_openloop -> description = resourceHelper.gs(R.string.openloop)
R.id.overview_disable -> description = resourceHelper.gs(R.string.disableloop)
R.id.overview_enable -> description = resourceHelper.gs(R.string.enableloop)
R.id.overview_resume -> description = resourceHelper.gs(R.string.resume)
R.id.overview_reconnect -> description = resourceHelper.gs(R.string.reconnect)
R.id.overview_suspend_1h -> description = resourceHelper.gs(R.string.suspendloopfor1h)
R.id.overview_suspend_2h -> description = resourceHelper.gs(R.string.suspendloopfor2h)
R.id.overview_suspend_3h -> description = resourceHelper.gs(R.string.suspendloopfor3h)
R.id.overview_suspend_10h -> description = resourceHelper.gs(R.string.suspendloopfor10h)
R.id.overview_closeloop -> description = resourceHelper.gs(R.string.closedloop)
R.id.overview_lgsloop -> description = resourceHelper.gs(R.string.lowglucosesuspend)
R.id.overview_openloop -> description = resourceHelper.gs(R.string.openloop)
R.id.overview_disable -> description = resourceHelper.gs(R.string.disableloop)
R.id.overview_enable -> description = resourceHelper.gs(R.string.enableloop)
R.id.overview_resume -> description = resourceHelper.gs(R.string.resume)
R.id.overview_reconnect -> description = resourceHelper.gs(R.string.reconnect)
R.id.overview_suspend_1h -> description = resourceHelper.gs(R.string.suspendloopfor1h)
R.id.overview_suspend_2h -> description = resourceHelper.gs(R.string.suspendloopfor2h)
R.id.overview_suspend_3h -> description = resourceHelper.gs(R.string.suspendloopfor3h)
R.id.overview_suspend_10h -> description = resourceHelper.gs(R.string.suspendloopfor10h)
R.id.overview_disconnect_15m -> description = resourceHelper.gs(R.string.disconnectpumpfor15m)
R.id.overview_disconnect_30m -> description = resourceHelper.gs(R.string.disconnectpumpfor30m)
R.id.overview_disconnect_1h -> description = resourceHelper.gs(R.string.disconnectpumpfor1h)
R.id.overview_disconnect_2h -> description = resourceHelper.gs(R.string.disconnectpumpfor2h)
R.id.overview_disconnect_3h -> description = resourceHelper.gs(R.string.disconnectpumpfor3h)
R.id.overview_disconnect_1h -> description = resourceHelper.gs(R.string.disconnectpumpfor1h)
R.id.overview_disconnect_2h -> description = resourceHelper.gs(R.string.disconnectpumpfor2h)
R.id.overview_disconnect_3h -> description = resourceHelper.gs(R.string.disconnectpumpfor3h)
}
activity?.let { activity ->
OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.confirm), description, Runnable {
@ -238,35 +280,34 @@ class LoopDialog : DaggerDialogFragment() {
}
fun onClick(v: View): Boolean {
val profile = profileFunction.getProfile() ?: return true
when (v.id) {
R.id.overview_closeloop -> {
R.id.overview_closeloop -> {
uel.log(Action.CLOSED_LOOP_MODE, Sources.LoopDialog)
sp.putString(R.string.key_aps_mode, "closed")
rxBus.send(EventPreferenceChange(resourceHelper.gs(R.string.closedloop)))
return true
}
R.id.overview_lgsloop -> {
R.id.overview_lgsloop -> {
uel.log(Action.LGS_LOOP_MODE, Sources.LoopDialog)
sp.putString(R.string.key_aps_mode, "lgs")
rxBus.send(EventPreferenceChange(resourceHelper.gs(R.string.lowglucosesuspend)))
return true
}
R.id.overview_openloop -> {
R.id.overview_openloop -> {
uel.log(Action.OPEN_LOOP_MODE, Sources.LoopDialog)
sp.putString(R.string.key_aps_mode, "open")
rxBus.send(EventPreferenceChange(resourceHelper.gs(R.string.lowglucosesuspend)))
return true
}
R.id.overview_disable -> {
R.id.overview_disable -> {
uel.log(Action.LOOP_DISABLED, Sources.LoopDialog)
loopPlugin.setPluginEnabled(PluginType.LOOP, false)
loopPlugin.setFragmentVisible(PluginType.LOOP, false)
configBuilder.storeSettings("DisablingLoop")
rxBus.send(EventRefreshOverview("suspendmenu"))
rxBus.send(EventRefreshOverview("suspend_menu"))
commandQueue.cancelTempBasal(true, object : Callback() {
override fun run() {
if (!result.success) {
@ -274,97 +315,121 @@ 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
}
R.id.overview_enable -> {
R.id.overview_enable -> {
uel.log(Action.LOOP_ENABLED, Sources.LoopDialog)
loopPlugin.setPluginEnabled(PluginType.LOOP, true)
loopPlugin.setFragmentVisible(PluginType.LOOP, true)
configBuilder.storeSettings("EnablingLoop")
rxBus.send(EventRefreshOverview("suspendmenu"))
loopPlugin.createOfflineEvent(0)
rxBus.send(EventRefreshOverview("suspend_menu"))
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
}
R.id.overview_resume, R.id.overview_reconnect -> {
uel.log(if (v.id==R.id.overview_resume) Action.RESUME else Action.RECONNECT, Sources.LoopDialog)
loopPlugin.suspendTo(0L)
rxBus.send(EventRefreshOverview("suspendmenu"))
uel.log(if (v.id == R.id.overview_resume) Action.RESUME else Action.RECONNECT, Sources.LoopDialog)
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("suspend_menu"))
commandQueue.cancelTempBasal(true, object : Callback() {
override fun run() {
if (!result.success) {
ErrorHelperActivity.runAlarm(ctx, result.comment, resourceHelper.gs(R.string.tempbasaldeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror)
ErrorHelperActivity.runAlarm(ctx, result.comment, resourceHelper.gs(R.string.tempbasaldeliveryerror), R.raw.boluserror)
}
}
})
sp.putBoolean(R.string.key_objectiveusereconnect, true)
loopPlugin.createOfflineEvent(0)
return true
}
R.id.overview_suspend_1h -> {
R.id.overview_suspend_1h -> {
uel.log(Action.SUSPEND, Sources.LoopDialog, ValueWithUnit.Hour(1))
loopPlugin.suspendLoop(60)
rxBus.send(EventRefreshOverview("suspendmenu"))
loopPlugin.suspendLoop(T.hours(1).mins().toInt())
rxBus.send(EventRefreshOverview("suspend_menu"))
return true
}
R.id.overview_suspend_2h -> {
R.id.overview_suspend_2h -> {
uel.log(Action.SUSPEND, Sources.LoopDialog, ValueWithUnit.Hour(2))
loopPlugin.suspendLoop(120)
rxBus.send(EventRefreshOverview("suspendmenu"))
loopPlugin.suspendLoop(T.hours(2).mins().toInt())
rxBus.send(EventRefreshOverview("suspend_menu"))
return true
}
R.id.overview_suspend_3h -> {
R.id.overview_suspend_3h -> {
uel.log(Action.SUSPEND, Sources.LoopDialog, ValueWithUnit.Hour(3))
loopPlugin.suspendLoop(180)
rxBus.send(EventRefreshOverview("suspendmenu"))
loopPlugin.suspendLoop(T.hours(3).mins().toInt())
rxBus.send(EventRefreshOverview("suspend_menu"))
return true
}
R.id.overview_suspend_10h -> {
R.id.overview_suspend_10h -> {
uel.log(Action.SUSPEND, Sources.LoopDialog, ValueWithUnit.Hour(10))
loopPlugin.suspendLoop(600)
rxBus.send(EventRefreshOverview("suspendmenu"))
loopPlugin.suspendLoop(T.hours(10).mins().toInt())
rxBus.send(EventRefreshOverview("suspend_menu"))
return true
}
R.id.overview_disconnect_15m -> {
uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Minute(15))
loopPlugin.disconnectPump(15, profile)
rxBus.send(EventRefreshOverview("suspendmenu"))
R.id.overview_disconnect_15m -> {
profileFunction.getProfile()?.let { profile ->
uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Minute(15))
loopPlugin.goToZeroTemp(T.mins(15).mins().toInt(), profile, OfflineEvent.Reason.DISCONNECT_PUMP)
rxBus.send(EventRefreshOverview("suspend_menu"))
}
return true
}
R.id.overview_disconnect_30m -> {
uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Minute(30))
loopPlugin.disconnectPump(30, profile)
rxBus.send(EventRefreshOverview("suspendmenu"))
R.id.overview_disconnect_30m -> {
profileFunction.getProfile()?.let { profile ->
uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Minute(30))
loopPlugin.goToZeroTemp(T.mins(30).mins().toInt(), profile, OfflineEvent.Reason.DISCONNECT_PUMP)
rxBus.send(EventRefreshOverview("suspend_menu"))
}
return true
}
R.id.overview_disconnect_1h -> {
uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Hour(1))
loopPlugin.disconnectPump(60, profile)
sp.putBoolean(R.string.key_objectiveusedisconnect, true)
rxBus.send(EventRefreshOverview("suspendmenu"))
R.id.overview_disconnect_1h -> {
profileFunction.getProfile()?.let { profile ->
uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Hour(1))
loopPlugin.goToZeroTemp(T.hours(1).mins().toInt(), profile, OfflineEvent.Reason.DISCONNECT_PUMP)
rxBus.send(EventRefreshOverview("suspend_menu"))
}
return true
}
R.id.overview_disconnect_2h -> {
uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Hour(2))
loopPlugin.disconnectPump(120, profile)
rxBus.send(EventRefreshOverview("suspendmenu"))
R.id.overview_disconnect_2h -> {
profileFunction.getProfile()?.let { profile ->
uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Hour(2))
loopPlugin.goToZeroTemp(T.hours(2).mins().toInt(), profile, OfflineEvent.Reason.DISCONNECT_PUMP)
rxBus.send(EventRefreshOverview("suspend_menu"))
}
return true
}
R.id.overview_disconnect_3h -> {
uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Hour(3))
loopPlugin.disconnectPump(180, profile)
rxBus.send(EventRefreshOverview("suspendmenu"))
R.id.overview_disconnect_3h -> {
profileFunction.getProfile()?.let { profile ->
uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Hour(3))
loopPlugin.goToZeroTemp(T.hours(3).mins().toInt(), profile, OfflineEvent.Reason.DISCONNECT_PUMP)
rxBus.send(EventRefreshOverview("suspend_menu"))
}
return true
}
}
@ -378,7 +443,7 @@ class LoopDialog : DaggerDialogFragment() {
it.commitAllowingStateLoss()
}
} catch (e: IllegalStateException) {
aapsLogger.debug(e.localizedMessage)
aapsLogger.debug(e.localizedMessage ?: e.toString())
}
}
}

View file

@ -1,6 +1,8 @@
package info.nightscout.androidaps.dialogs
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -8,20 +10,32 @@ import android.widget.ArrayAdapter
import com.google.common.base.Joiner
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.ProfileSealed
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.TemporaryTarget
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.database.transactions.InsertAndCancelCurrentTemporaryTargetTransaction
import info.nightscout.androidaps.databinding.DialogProfileswitchBinding
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import java.text.DecimalFormat
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class ProfileSwitchDialog : DialogFragmentWithDate() {
@ -31,6 +45,10 @@ class ProfileSwitchDialog : DialogFragmentWithDate() {
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var repository: AppRepository
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var config: Config
@Inject lateinit var hardLimits: HardLimits
@Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var defaultValueHelper: DefaultValueHelper
private var profileIndex: Int? = null
@ -42,6 +60,17 @@ class ProfileSwitchDialog : DialogFragmentWithDate() {
// onDestroyView.
private val binding get() = _binding!!
private val textWatcher: TextWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable) {
val isDuration = binding.duration.value > 0
val isLowerPercentage = binding.percentage.value < 100
binding.ttLayout.visibility = (isDuration && isLowerPercentage).toVisibility()
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
savedInstanceState.putDouble("duration", binding.duration.value)
@ -63,9 +92,11 @@ class ProfileSwitchDialog : DialogFragmentWithDate() {
super.onViewCreated(view, savedInstanceState)
binding.duration.setParams(savedInstanceState?.getDouble("duration")
?: 0.0, 0.0, Constants.MAX_PROFILE_SWITCH_DURATION, 10.0, DecimalFormat("0"), false, binding.okcancel.ok)
?: 0.0, 0.0, Constants.MAX_PROFILE_SWITCH_DURATION, 10.0, DecimalFormat("0"), false, binding.okcancel.ok,
textWatcher)
binding.percentage.setParams(savedInstanceState?.getDouble("percentage")
?: 100.0, Constants.CPP_MIN_PERCENTAGE.toDouble(), Constants.CPP_MAX_PERCENTAGE.toDouble(), 5.0, DecimalFormat("0"), false, binding.okcancel.ok)
?: 100.0, Constants.CPP_MIN_PERCENTAGE.toDouble(), Constants.CPP_MAX_PERCENTAGE.toDouble(), 5.0,
DecimalFormat("0"), false, binding.okcancel.ok, textWatcher)
binding.timeshift.setParams(savedInstanceState?.getDouble("timeshift")
?: 0.0, Constants.CPP_MIN_TIMESHIFT.toDouble(), Constants.CPP_MAX_TIMESHIFT.toDouble(), 1.0, DecimalFormat("0"), false, binding.okcancel.ok)
@ -97,6 +128,7 @@ class ProfileSwitchDialog : DialogFragmentWithDate() {
binding.reuselayout.visibility = View.GONE
}
}
binding.ttLayout.visibility = View.GONE
}
override fun onDestroyView() {
@ -128,23 +160,58 @@ class ProfileSwitchDialog : DialogFragmentWithDate() {
if (eventTimeChanged)
actions.add(resourceHelper.gs(R.string.time) + ": " + dateUtil.dateAndTimeString(eventTime))
val isTT = binding.duration.value > 0 && binding.percentage.value < 100 && binding.tt.isChecked
val target = defaultValueHelper.determineActivityTT()
val units = profileFunction.getUnits()
if (isTT)
actions.add(resourceHelper.gs(R.string.careportal_temporarytarget) + ": " + resourceHelper.gs(R.string.activity))
activity?.let { activity ->
OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.careportal_profileswitch), HtmlHelper.fromHtml(Joiner.on("<br/>").join(actions)), {
profileFunction.createProfileSwitch(profileStore,
profileName = profileName,
durationInMinutes = duration,
percentage = percent,
timeShiftInHours = timeShift,
timestamp = eventTime)
uel.log(Action.PROFILE_SWITCH,
Sources.ProfileSwitchDialog,
notes,
ValueWithUnit.Timestamp(eventTime).takeIf { eventTimeChanged },
ValueWithUnit.SimpleString(profileName),
ValueWithUnit.Percent(percent),
ValueWithUnit.Hour(timeShift).takeIf { timeShift != 0 },
ValueWithUnit.Minute(duration).takeIf { duration != 0 })
})
val ps = profileFunction.buildProfileSwitch(profileStore, profileName, duration, percent, timeShift, eventTime)
val validity = ProfileSealed.PS(ps).isValid(resourceHelper.gs(R.string.careportal_profileswitch), activePlugin.activePump, config, resourceHelper, rxBus, hardLimits)
if (validity.isValid)
OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.careportal_profileswitch), HtmlHelper.fromHtml(Joiner.on("<br/>").join(actions)), {
profileFunction.createProfileSwitch(profileStore,
profileName = profileName,
durationInMinutes = duration,
percentage = percent,
timeShiftInHours = timeShift,
timestamp = eventTime)
uel.log(Action.PROFILE_SWITCH,
Sources.ProfileSwitchDialog,
notes,
ValueWithUnit.Timestamp(eventTime).takeIf { eventTimeChanged },
ValueWithUnit.SimpleString(profileName),
ValueWithUnit.Percent(percent),
ValueWithUnit.Hour(timeShift).takeIf { timeShift != 0 },
ValueWithUnit.Minute(duration).takeIf { duration != 0 })
if (isTT) {
disposable += repository.runTransactionForResult(
InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = eventTime,
duration = TimeUnit.MINUTES.toMillis(duration.toLong()),
reason = TemporaryTarget.Reason.ACTIVITY,
lowTarget = Profile.toMgdl(target, profileFunction.getUnits()),
highTarget = Profile.toMgdl(target, profileFunction.getUnits())
)
).subscribe({ result ->
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted temp target $it") }
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated temp target $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it)
})
uel.log(Action.TT, Sources.TTDialog, ValueWithUnit.Timestamp(eventTime).takeIf { eventTimeChanged }, ValueWithUnit.TherapyEventTTReason(
TemporaryTarget.Reason.ACTIVITY), ValueWithUnit.fromGlucoseUnit(target, units.asText), ValueWithUnit.Minute(duration))
}
})
else {
OKDialog.show(
activity,
resourceHelper.gs(R.string.careportal_profileswitch),
HtmlHelper.fromHtml(Joiner.on("<br/>").join(validity.reasons))
)
return false
}
}
return true
}

View file

@ -119,7 +119,7 @@ class TempBasalDialog : DialogFragmentWithDate() {
val callback: Callback = object : Callback() {
override fun run() {
if (!result.success) {
ErrorHelperActivity.runAlarm(ctx, result.comment, resourceHelper.gs(R.string.tempbasaldeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror)
ErrorHelperActivity.runAlarm(ctx, result.comment, resourceHelper.gs(R.string.tempbasaldeliveryerror), R.raw.boluserror)
}
}
}

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.Sources
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.interfaces.GlucoseUnit
import info.nightscout.androidaps.interfaces.ProfileFunction
@ -196,7 +196,7 @@ class TempTargetDialog : DialogFragmentWithDate() {
aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it)
})
} else {
disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction(
disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = eventTime,
duration = TimeUnit.MINUTES.toMillis(duration.toLong()),
reason = when (reason) {

View file

@ -174,7 +174,7 @@ class TreatmentDialog : DialogFragmentWithDate() {
commandQueue.bolus(detailedBolusInfo, object : Callback() {
override fun run() {
if (!result.success) {
ErrorHelperActivity.runAlarm(ctx, result.comment, resourceHelper.gs(R.string.treatmentdeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror)
ErrorHelperActivity.runAlarm(ctx, result.comment, resourceHelper.gs(R.string.treatmentdeliveryerror), R.raw.boluserror)
}
}
})

View file

@ -384,7 +384,7 @@ class WizardDialog : DaggerDialogFragment() {
it.commitAllowingStateLoss()
}
} catch (e: IllegalStateException) {
aapsLogger.debug(e.localizedMessage)
aapsLogger.debug(e.localizedMessage ?: "")
}
}
}

View file

@ -0,0 +1,3 @@
package info.nightscout.androidaps.events
class EventTreatmentUpdateGui : EventUpdateGui()

View file

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

View file

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

View file

@ -1,6 +1,6 @@
package info.nightscout.androidaps.plugins.aps.logger
import info.nightscout.androidaps.db.StaticInjector
import info.nightscout.androidaps.di.StaticInjector
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import org.mozilla.javascript.ScriptableObject

View file

@ -12,22 +12,24 @@ import androidx.core.app.NotificationCompat
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.*
import info.nightscout.androidaps.activities.ErrorHelperActivity
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.androidaps.data.DetailedBolusInfo
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.data.PumpEnactResult
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.Sources
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.events.EventAcceptOpenLoopChange
import info.nightscout.androidaps.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.events.EventNewBG
import info.nightscout.androidaps.events.EventTempTargetChange
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.LTag
import info.nightscout.androidaps.logging.UserEntryLogger
@ -44,7 +46,6 @@ import info.nightscout.androidaps.plugins.general.wear.events.EventWearConfirmAc
import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.queue.commands.Command
import info.nightscout.androidaps.receivers.ReceiverStatusStore
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
@ -64,8 +65,9 @@ import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.abs
@OpenForTesting
@Singleton
open class LoopPlugin @Inject constructor(
class LoopPlugin @Inject constructor(
injector: HasAndroidInjector,
aapsLogger: AAPSLogger,
private val aapsSchedulers: AapsSchedulers,
@ -96,7 +98,7 @@ open class LoopPlugin @Inject constructor(
.enableByDefault(config.APS)
.description(R.string.description_loop),
aapsLogger, resourceHelper, injector
), LoopInterface {
), Loop {
private val disposable = CompositeDisposable()
private var lastBgTriggeredRun: Long = 0
@ -158,48 +160,21 @@ open class LoopPlugin @Inject constructor(
}
}
override fun suspendTo(endTime: Long) {
sp.putLong("loopSuspendedTill", endTime)
sp.putBoolean("isSuperBolus", false)
sp.putBoolean("isDisconnected", false)
override fun minutesToEndOfSuspend(): Int {
val offlineEventWrapped = repository.getOfflineEventActiveAt(dateUtil.now()).blockingGet()
return if (offlineEventWrapped is ValueWrapper.Existing) T.msecs(offlineEventWrapped.value.timestamp + offlineEventWrapped.value.duration - dateUtil.now()).mins().toInt()
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
get() {
val loopSuspendedTill = sp.getLong("loopSuspendedTill", 0L)
if (loopSuspendedTill == 0L) return false
val now = System.currentTimeMillis()
if (loopSuspendedTill <= now) { // time exceeded
suspendTo(0L)
return false
}
return true
get() = repository.getOfflineEventActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing
override var enabled: Boolean
get() = isEnabled()
set(value) {
setPluginEnabled(PluginType.LOOP, value)
}
val isLGS: Boolean
get() {
val closedLoopEnabled = constraintChecker.isClosedLoopAllowed()
@ -211,30 +186,16 @@ open class LoopPlugin @Inject constructor(
return isLGS
}
// time exceeded
val isSuperBolus: Boolean
get() {
val loopSuspendedTill = sp.getLong("loopSuspendedTill", 0L)
if (loopSuspendedTill == 0L) return false
val now = System.currentTimeMillis()
if (loopSuspendedTill <= now) { // time exceeded
suspendTo(0L)
return false
}
return sp.getBoolean("isSuperBolus", false)
val offlineEventWrapped = repository.getOfflineEventActiveAt(dateUtil.now()).blockingGet()
return offlineEventWrapped is ValueWrapper.Existing && offlineEventWrapped.value.reason == OfflineEvent.Reason.SUPER_BOLUS
}
// time exceeded
val isDisconnected: Boolean
get() {
val loopSuspendedTill = sp.getLong("loopSuspendedTill", 0L)
if (loopSuspendedTill == 0L) return false
val now = System.currentTimeMillis()
if (loopSuspendedTill <= now) { // time exceeded
suspendTo(0L)
return false
}
return sp.getBoolean("isDisconnected", false)
val offlineEventWrapped = repository.getOfflineEventActiveAt(dateUtil.now()).blockingGet()
return offlineEventWrapped is ValueWrapper.Existing && offlineEventWrapped.value.reason == OfflineEvent.Reason.DISCONNECT_PUMP
}
@Suppress("SameParameterValue")
@ -251,6 +212,17 @@ open class LoopPlugin @Inject constructor(
invoke(initiator, allowNotification, false)
}
@Synchronized
fun isEmptyQueue(): Boolean {
val maxMinutes = 2L
val start = dateUtil.now()
while (start + T.mins(maxMinutes).msecs() > dateUtil.now()) {
if (commandQueue.size() == 0 && commandQueue.performing() == null) return true
SystemClock.sleep(100)
}
return false
}
@Synchronized
operator fun invoke(initiator: String, allowNotification: Boolean, tempBasalFallback: Boolean) {
try {
@ -289,6 +261,12 @@ open class LoopPlugin @Inject constructor(
return
} else rxBus.send(EventLoopInvoked())
if (!isEmptyQueue()) {
aapsLogger.debug(LTag.APS, resourceHelper.gs(R.string.pumpbusy))
rxBus.send(EventLoopSetLastRunGui(resourceHelper.gs(R.string.pumpbusy)))
return
}
// Prepare for pumps using % basals
if (pump.pumpDescription.tempBasalStyle == PumpDescription.PERCENT && allowPercentage()) {
apsResult.usePercent = true
@ -353,15 +331,15 @@ open class LoopPlugin @Inject constructor(
if (sp.getBoolean(R.string.key_enable_carbs_required_alert_local, true) && sp.getBoolean(R.string.key_raise_notifications_as_android_notifications, true)) {
val intentAction5m = Intent(context, CarbSuggestionReceiver::class.java)
intentAction5m.putExtra("ignoreDuration", 5)
val pendingIntent5m = PendingIntent.getBroadcast(context, 1, intentAction5m, PendingIntent.FLAG_UPDATE_CURRENT)
val pendingIntent5m = PendingIntent.getBroadcast(context, 1, intentAction5m, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
val actionIgnore5m = NotificationCompat.Action(R.drawable.ic_notif_aaps, resourceHelper.gs(R.string.ignore5m, "Ignore 5m"), pendingIntent5m)
val intentAction15m = Intent(context, CarbSuggestionReceiver::class.java)
intentAction15m.putExtra("ignoreDuration", 15)
val pendingIntent15m = PendingIntent.getBroadcast(context, 1, intentAction15m, PendingIntent.FLAG_UPDATE_CURRENT)
val pendingIntent15m = PendingIntent.getBroadcast(context, 1, intentAction15m, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
val actionIgnore15m = NotificationCompat.Action(R.drawable.ic_notif_aaps, resourceHelper.gs(R.string.ignore15m, "Ignore 15m"), pendingIntent15m)
val intentAction30m = Intent(context, CarbSuggestionReceiver::class.java)
intentAction30m.putExtra("ignoreDuration", 30)
val pendingIntent30m = PendingIntent.getBroadcast(context, 1, intentAction30m, PendingIntent.FLAG_UPDATE_CURRENT)
val pendingIntent30m = PendingIntent.getBroadcast(context, 1, intentAction30m, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
val actionIgnore30m = NotificationCompat.Action(R.drawable.ic_notif_aaps, resourceHelper.gs(R.string.ignore30m, "Ignore 30m"), pendingIntent30m)
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
builder.setSmallIcon(R.drawable.notif_icon)
@ -399,8 +377,7 @@ open class LoopPlugin @Inject constructor(
}
}
if (resultAfterConstraints.isChangeRequested
&& !commandQueue.bolusInQueue()
&& !commandQueue.isRunning(Command.CommandType.BOLUS)) {
&& !commandQueue.bolusInQueue()) {
val waiting = PumpEnactResult(injector)
waiting.queued = true
if (resultAfterConstraints.tempBasalRequested) lastRun.tbrSetByPump = waiting
@ -483,7 +460,7 @@ open class LoopPlugin @Inject constructor(
stackBuilder.addParentStack(MainActivity::class.java)
// Adds the Intent that starts the Activity to the top of the stack
stackBuilder.addNextIntent(resultIntent)
val resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
val resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
builder.setContentIntent(resultPendingIntent)
builder.setVibrate(longArrayOf(1000, 1000, 1000, 1000, 1000))
val mNotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@ -642,22 +619,28 @@ open class LoopPlugin @Inject constructor(
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
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) {
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() {
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), R.raw.boluserror)
}
}
})
} 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() {
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), R.raw.boluserror)
}
}
})
@ -666,37 +649,28 @@ open class LoopPlugin @Inject constructor(
commandQueue.cancelExtended(object : Callback() {
override fun run() {
if (!result.success) {
ErrorHelperActivity.runAlarm(context, result.comment, resourceHelper.gs(R.string.extendedbolusdeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror)
ErrorHelperActivity.runAlarm(context, result.comment, resourceHelper.gs(R.string.extendedbolusdeliveryerror), R.raw.boluserror)
}
}
})
}
createOfflineEvent(durationInMinutes)
}
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() {
override fun run() {
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), R.raw.boluserror)
}
}
})
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 {

View file

@ -17,7 +17,6 @@ import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback
import info.nightscout.androidaps.plugins.aps.loop.ScriptReader
import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus
import info.nightscout.androidaps.utils.sharedPreferences.SP
import org.json.JSONArray
@ -40,7 +39,6 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
@Inject lateinit var sp: SP
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var iobCobCalculator: IobCobCalculator
@Inject lateinit var openHumansUploader: OpenHumansUploader
private val mScriptReader: ScriptReader
private var profile = JSONObject()
@ -116,7 +114,6 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
aapsLogger.debug(LTag.APS, "Result: $result")
try {
val resultJson = JSONObject(result)
openHumansUploader.enqueueAMAData(profile, glucoseStatus, iobData, mealData, currentTemp, autosensData, resultJson)
determineBasalResultAMA = DetermineBasalResultAMA(injector, jsResult, resultJson)
} catch (e: JSONException) {
aapsLogger.error(LTag.APS, "Unhandled exception", e)

View file

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

View file

@ -3,6 +3,7 @@ package info.nightscout.androidaps.plugins.aps.openAPSAMA
import android.content.Context
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
@ -27,8 +28,9 @@ import org.json.JSONException
import javax.inject.Inject
import javax.inject.Singleton
@OpenForTesting
@Singleton
open class OpenAPSAMAPlugin @Inject constructor(
class OpenAPSAMAPlugin @Inject constructor(
injector: HasAndroidInjector,
aapsLogger: AAPSLogger,
private val rxBus: RxBusWrapper,
@ -123,11 +125,11 @@ open class OpenAPSAMAPlugin @Inject constructor(
maxBg = hardLimits.verifyHardLimits(tempTarget.value.highTarget, R.string.temp_target_high_target, HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1].toDouble())
targetBg = hardLimits.verifyHardLimits(tempTarget.value.target(), R.string.temp_target_value, HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1].toDouble())
}
if (!hardLimits.checkOnlyHardLimits(profile.dia, R.string.profile_dia, hardLimits.minDia(), hardLimits.maxDia())) return
if (!hardLimits.checkOnlyHardLimits(profile.getIcTimeFromMidnight(Profile.secondsFromMidnight()), R.string.profile_carbs_ratio_value, hardLimits.minIC(), hardLimits.maxIC())) return
if (!hardLimits.checkOnlyHardLimits(profile.getIsfMgdl(), R.string.profile_sensitivity_value, HardLimits.MIN_ISF, HardLimits.MAX_ISF)) return
if (!hardLimits.checkOnlyHardLimits(profile.getMaxDailyBasal(), R.string.profile_max_daily_basal_value, 0.02, hardLimits.maxBasal())) return
if (!hardLimits.checkOnlyHardLimits(pump.baseBasalRate, R.string.current_basal_value, 0.01, hardLimits.maxBasal())) return
if (!hardLimits.checkHardLimits(profile.dia, R.string.profile_dia, hardLimits.minDia(), hardLimits.maxDia())) return
if (!hardLimits.checkHardLimits(profile.getIcTimeFromMidnight(Profile.secondsFromMidnight()), R.string.profile_carbs_ratio_value, hardLimits.minIC(), hardLimits.maxIC())) return
if (!hardLimits.checkHardLimits(profile.getIsfMgdl(), R.string.profile_sensitivity_value, HardLimits.MIN_ISF, HardLimits.MAX_ISF)) return
if (!hardLimits.checkHardLimits(profile.getMaxDailyBasal(), R.string.profile_max_daily_basal_value, 0.02, hardLimits.maxBasal())) return
if (!hardLimits.checkHardLimits(pump.baseBasalRate, R.string.current_basal_value, 0.01, hardLimits.maxBasal())) return
startPart = System.currentTimeMillis()
if (constraintChecker.isAutosensModeEnabled().value()) {
val autosensData = iobCobCalculator.getLastAutosensDataWithWaitForCalculationFinish("OpenAPSPlugin")

View file

@ -17,7 +17,6 @@ import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback
import info.nightscout.androidaps.plugins.aps.loop.ScriptReader
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus
import info.nightscout.androidaps.utils.SafeParse
import info.nightscout.androidaps.utils.resources.ResourceHelper
@ -41,7 +40,6 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var iobCobCalculator: IobCobCalculator
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var openHumansUploader: OpenHumansUploader
private var profile = JSONObject()
private var mGlucoseStatus = JSONObject()
@ -129,7 +127,6 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
aapsLogger.debug(LTag.APS, "Result: $result")
try {
val resultJson = JSONObject(result)
openHumansUploader.enqueueSMBData(profile, mGlucoseStatus, iobData, mealData, currentTemp, autosensData, microBolusAllowed, smbAlwaysAllowed, resultJson)
determineBasalResultSMB = DetermineBasalResultSMB(injector, resultJson)
} catch (e: JSONException) {
aapsLogger.error(LTag.APS, "Unhandled exception", e)

View file

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

View file

@ -5,6 +5,7 @@ import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.extensions.target
@ -27,8 +28,9 @@ import info.nightscout.androidaps.utils.sharedPreferences.SP
import javax.inject.Inject
import javax.inject.Singleton
@OpenForTesting
@Singleton
open class OpenAPSSMBPlugin @Inject constructor(
class OpenAPSSMBPlugin @Inject constructor(
injector: HasAndroidInjector,
aapsLogger: AAPSLogger,
private val rxBus: RxBusWrapper,
@ -128,11 +130,11 @@ open class OpenAPSSMBPlugin @Inject constructor(
maxBg = hardLimits.verifyHardLimits(tempTarget.value.highTarget, R.string.temp_target_high_target, HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1].toDouble())
targetBg = hardLimits.verifyHardLimits(tempTarget.value.target(), R.string.temp_target_value, HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1].toDouble())
}
if (!hardLimits.checkOnlyHardLimits(profile.dia, R.string.profile_dia, hardLimits.minDia(), hardLimits.maxDia())) return
if (!hardLimits.checkOnlyHardLimits(profile.getIcTimeFromMidnight(Profile.secondsFromMidnight()), R.string.profile_carbs_ratio_value, hardLimits.minIC(), hardLimits.maxIC())) return
if (!hardLimits.checkOnlyHardLimits(profile.getIsfMgdl(), R.string.profile_sensitivity_value, HardLimits.MIN_ISF, HardLimits.MAX_ISF)) return
if (!hardLimits.checkOnlyHardLimits(profile.getMaxDailyBasal(), R.string.profile_max_daily_basal_value, 0.02, hardLimits.maxBasal())) return
if (!hardLimits.checkOnlyHardLimits(pump.baseBasalRate, R.string.current_basal_value, 0.01, hardLimits.maxBasal())) return
if (!hardLimits.checkHardLimits(profile.dia, R.string.profile_dia, hardLimits.minDia(), hardLimits.maxDia())) return
if (!hardLimits.checkHardLimits(profile.getIcTimeFromMidnight(Profile.secondsFromMidnight()), R.string.profile_carbs_ratio_value, hardLimits.minIC(), hardLimits.maxIC())) return
if (!hardLimits.checkHardLimits(profile.getIsfMgdl(), R.string.profile_sensitivity_value, HardLimits.MIN_ISF, HardLimits.MAX_ISF)) return
if (!hardLimits.checkHardLimits(profile.getMaxDailyBasal(), R.string.profile_max_daily_basal_value, 0.02, hardLimits.maxBasal())) return
if (!hardLimits.checkHardLimits(pump.baseBasalRate, R.string.current_basal_value, 0.01, hardLimits.maxBasal())) return
startPart = System.currentTimeMillis()
if (constraintChecker.isAutosensModeEnabled().value()) {
val autosensData = iobCobCalculator.getLastAutosensDataWithWaitForCalculationFinish("OpenAPSPlugin")

View file

@ -114,7 +114,6 @@ class ConfigBuilderFragment : DaggerFragment() {
createViewsForPlugins(R.string.configbuilder_loop, R.string.configbuilder_loop_description, PluginType.LOOP, activePlugin.getSpecificPluginsVisibleInList(PluginType.LOOP))
createViewsForPlugins(R.string.constraints, R.string.configbuilder_constraints_description, PluginType.CONSTRAINTS, activePlugin.getSpecificPluginsVisibleInList(PluginType.CONSTRAINTS))
}
createViewsForPlugins(R.string.configbuilder_treatments, R.string.configbuilder_treatments_description, PluginType.TREATMENT, activePlugin.getSpecificPluginsVisibleInList(PluginType.TREATMENT))
createViewsForPlugins(R.string.configbuilder_general, R.string.configbuilder_general_description, PluginType.GENERAL, activePlugin.getSpecificPluginsVisibleInList(PluginType.GENERAL))
}
@ -139,22 +138,16 @@ class ConfigBuilderFragment : DaggerFragment() {
@Suppress("InflateParams")
val baseView: LinearLayout = fragment.layoutInflater.inflate(R.layout.configbuilder_single_plugin, null) as LinearLayout
private val enabledExclusive: RadioButton
private val enabledInclusive: CheckBox
private val pluginIcon: ImageView
private val pluginName: TextView
private val pluginDescription: TextView
private val pluginPreferences: ImageButton
private val pluginVisibility: CheckBox
private val enabledExclusive: RadioButton = baseView.findViewById(R.id.plugin_enabled_exclusive)
private val enabledInclusive: CheckBox = baseView.findViewById(R.id.plugin_enabled_inclusive)
private val pluginIcon: ImageView = baseView.findViewById(R.id.plugin_icon)
private val pluginIcon2: ImageView = baseView.findViewById(R.id.plugin_icon2)
private val pluginName: TextView = baseView.findViewById(R.id.plugin_name)
private val pluginDescription: TextView = baseView.findViewById(R.id.plugin_description)
private val pluginPreferences: ImageButton = baseView.findViewById(R.id.plugin_preferences)
private val pluginVisibility: CheckBox = baseView.findViewById(R.id.plugin_visibility)
init {
enabledExclusive = baseView.findViewById(R.id.plugin_enabled_exclusive)
enabledInclusive = baseView.findViewById(R.id.plugin_enabled_inclusive)
pluginIcon = baseView.findViewById(R.id.plugin_icon)
pluginName = baseView.findViewById(R.id.plugin_name)
pluginDescription = baseView.findViewById(R.id.plugin_description)
pluginPreferences = baseView.findViewById(R.id.plugin_preferences)
pluginVisibility = baseView.findViewById(R.id.plugin_visibility)
pluginVisibility.setOnClickListener {
plugin.setFragmentVisible(pluginType, pluginVisibility.isChecked)
@ -192,6 +185,12 @@ class ConfigBuilderFragment : DaggerFragment() {
if (plugin.menuIcon != -1) {
pluginIcon.visibility = View.VISIBLE
pluginIcon.setImageDrawable(context?.let { ContextCompat.getDrawable(it, plugin.menuIcon) })
if (plugin.menuIcon2 != -1) {
pluginIcon2.visibility = View.VISIBLE
pluginIcon2.setImageDrawable(context?.let { ContextCompat.getDrawable(it, plugin.menuIcon2) })
} else {
pluginIcon2.visibility = View.GONE
}
} else {
pluginIcon.visibility = View.GONE
}

View file

@ -120,7 +120,6 @@ class ConfigBuilderPlugin @Inject constructor(
for (p in activePlugin.getPluginsList()) {
aapsLogger.debug(LTag.CONFIGBUILDER, p.name + ":" +
(if (p.isEnabled(PluginType.GENERAL)) " GENERAL" else "") +
(if (p.isEnabled(PluginType.TREATMENT)) " TREATMENT" else "") +
(if (p.isEnabled(PluginType.SENSITIVITY)) " SENSITIVITY" else "") +
(if (p.isEnabled(PluginType.PROFILE)) " PROFILE" else "") +
(if (p.isEnabled(PluginType.APS)) " APS" else "") +
@ -135,7 +134,12 @@ class ConfigBuilderPlugin @Inject constructor(
// Ask when switching to physical pump plugin
fun switchAllowed(changedPlugin: PluginBase, newState: Boolean, activity: FragmentActivity?, type: PluginType) {
if (changedPlugin.getType() == PluginType.PUMP && changedPlugin.name != resourceHelper.gs(R.string.virtualpump)) confirmPumpPluginActivation(changedPlugin, newState, activity, type) else performPluginSwitch(changedPlugin, newState, type)
if (changedPlugin.getType() == PluginType.PUMP && changedPlugin.name != resourceHelper.gs(R.string.virtualpump))
confirmPumpPluginActivation(changedPlugin, newState, activity, type)
else if (changedPlugin.getType() == PluginType.PUMP) {
performPluginSwitch(changedPlugin, newState, type)
pumpSync.connectNewPump()
} else performPluginSwitch(changedPlugin, newState, type)
}
private fun confirmPumpPluginActivation(changedPlugin: PluginBase, newState: Boolean, activity: FragmentActivity?, type: PluginType) {
@ -185,7 +189,6 @@ class ConfigBuilderPlugin @Inject constructor(
PluginType.APS -> pluginsInCategory = activePlugin.getSpecificPluginsListByInterface(APS::class.java)
PluginType.PROFILE -> pluginsInCategory = activePlugin.getSpecificPluginsListByInterface(ProfileSource::class.java)
PluginType.BGSOURCE -> pluginsInCategory = activePlugin.getSpecificPluginsListByInterface(BgSource::class.java)
PluginType.TREATMENT -> pluginsInCategory = activePlugin.getSpecificPluginsListByInterface(TreatmentsInterface::class.java)
PluginType.PUMP -> pluginsInCategory = activePlugin.getSpecificPluginsListByInterface(Pump::class.java)
else -> {

View file

@ -1,6 +1,5 @@
package info.nightscout.androidaps.plugins.configBuilder
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
@ -21,7 +20,6 @@ class PluginStore @Inject constructor(
private var activeAPSStore: APS? = null
private var activeInsulinStore: Insulin? = null
private var activeSensitivityStore: Sensitivity? = null
private var activeTreatmentsStore: TreatmentsInterface? = null
fun loadDefaults() {
verifySelectionInCategories()
@ -121,16 +119,6 @@ class PluginStore @Inject constructor(
aapsLogger.debug(LTag.CONFIGBUILDER, "Defaulting PumpInterface")
}
setFragmentVisibilities((activePumpStore as PluginBase).name, pluginsInCategory, PluginType.PUMP)
// PluginType.TREATMENT
pluginsInCategory = getSpecificPluginsList(PluginType.TREATMENT)
activeTreatmentsStore = getTheOneEnabledInArray(pluginsInCategory, PluginType.TREATMENT) as TreatmentsInterface?
if (activeTreatmentsStore == null) {
activeTreatmentsStore = getDefaultPlugin(PluginType.TREATMENT) as TreatmentsInterface
(activeTreatmentsStore as PluginBase).setPluginEnabled(PluginType.TREATMENT, true)
aapsLogger.debug(LTag.CONFIGBUILDER, "Defaulting PumpInterface")
}
setFragmentVisibilities((activeTreatmentsStore as PluginBase).name, pluginsInCategory, PluginType.TREATMENT)
}
private fun setFragmentVisibilities(activePluginName: String, pluginsInCategory: ArrayList<PluginBase>,
@ -175,13 +163,12 @@ class PluginStore @Inject constructor(
get() = activeSensitivityStore
?: checkNotNull(activeSensitivityStore) { "No sensitivity selected" }
override val activeTreatments: TreatmentsInterface
get() = activeTreatmentsStore
?: checkNotNull(activeTreatmentsStore) { "No treatments selected" }
override val activeOverview: Overview
get() = getSpecificPluginsListByInterface(Overview::class.java).first() as Overview
override val activeSafety: Safety
get() = getSpecificPluginsListByInterface(Safety::class.java).first() as Safety
override fun getPluginsList(): ArrayList<PluginBase> = ArrayList(plugins)
}
}

View file

@ -9,14 +9,12 @@ import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.ProfileSwitch
import info.nightscout.androidaps.database.transactions.InsertOrUpdateProfileSwitch
import info.nightscout.androidaps.extensions.fromConstant
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.interfaces.ProfileStore
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
@ -30,10 +28,13 @@ import javax.inject.Singleton
class ProfileFunctionImplementation @Inject constructor(
private val aapsLogger: AAPSLogger,
private val sp: SP,
private val rxBus: RxBusWrapper,
private val resourceHelper: ResourceHelper,
private val activePlugin: ActivePlugin,
private val repository: AppRepository,
private val dateUtil: DateUtil
private val dateUtil: DateUtil,
private val config: Config,
private val hardLimits: HardLimits
) : ProfileFunction {
val cache = LongSparseArray<Profile>()
@ -67,14 +68,15 @@ class ProfileFunctionImplementation @Inject constructor(
override fun getProfile(): Profile? =
getProfile(dateUtil.now())
@Synchronized
override fun getProfile(time: Long): Profile? {
val rounded = time - time % 1000
val cached = cache[rounded]
if (cached != null) {
// aapsLogger.debug("XXXXXXXXXXXXXXX HIT getProfile for $time $rounded")
// aapsLogger.debug("HIT getProfile for $time $rounded")
return cached
}
// aapsLogger.debug("XXXXXXXXXXXXXXX getProfile called for $time")
// aapsLogger.debug("getProfile called for $time")
val ps = repository.getEffectiveProfileSwitchActiveAt(time).blockingGet()
if (ps is ValueWrapper.Existing) {
val sealed = ProfileSealed.EPS(ps.value)
@ -90,10 +92,10 @@ class ProfileFunctionImplementation @Inject constructor(
if (sp.getString(R.string.key_units, Constants.MGDL) == Constants.MGDL) GlucoseUnit.MGDL
else GlucoseUnit.MMOL
override fun createProfileSwitch(profileStore: ProfileStore, profileName: String, durationInMinutes: Int, percentage: Int, timeShiftInHours: Int, timestamp: Long) {
override fun buildProfileSwitch(profileStore: ProfileStore, profileName: String, durationInMinutes: Int, percentage: Int, timeShiftInHours: Int, timestamp: Long) : ProfileSwitch {
val pureProfile = profileStore.getSpecificProfile(profileName)
?: throw InvalidParameterSpecException(profileName)
val ps = ProfileSwitch(
return ProfileSwitch(
timestamp = timestamp,
basalBlocks = pureProfile.basalBlocks,
isfBlocks = pureProfile.isfBlocks,
@ -104,20 +106,49 @@ class ProfileFunctionImplementation @Inject constructor(
timeshift = T.hours(timeShiftInHours.toLong()).msecs(),
percentage = percentage,
duration = T.mins(durationInMinutes.toLong()).msecs(),
insulinConfiguration = activePlugin.activeInsulin.insulinConfiguration)
disposable += repository.runTransactionForResult(InsertOrUpdateProfileSwitch(ps))
.subscribe({ result ->
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted ProfileSwitch $it") }
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated ProfileSwitch $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving ProfileSwitch", it)
})
insulinConfiguration = activePlugin.activeInsulin.insulinConfiguration.also {
it.insulinEndTime = (pureProfile.dia * 3600 * 1000).toLong()
}
)
}
override fun createProfileSwitch(durationInMinutes: Int, percentage: Int, timeShiftInHours: Int) {
val profileStore = activePlugin.activeProfileSource.profile ?: return
val profileName = activePlugin.activeProfileSource.profile?.getDefaultProfileName()
?: return
createProfileSwitch(profileStore, profileName, durationInMinutes, percentage, timeShiftInHours, dateUtil.now())
override fun createProfileSwitch(profileStore: ProfileStore, profileName: String, durationInMinutes: Int, percentage: Int, timeShiftInHours: Int, timestamp: Long) {
val ps = buildProfileSwitch(profileStore, profileName, durationInMinutes, percentage, timeShiftInHours, timestamp)
disposable += repository.runTransactionForResult(InsertOrUpdateProfileSwitch(ps))
.subscribe({ result ->
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted ProfileSwitch $it") }
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated ProfileSwitch $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving ProfileSwitch", it)
})
}
override fun createProfileSwitch(durationInMinutes: Int, percentage: Int, timeShiftInHours: Int): Boolean {
val profile = repository.getPermanentProfileSwitch(dateUtil.now())
?: throw InvalidParameterSpecException("No active ProfileSwitch")
val profileStore = activePlugin.activeProfileSource.profile ?: return false
val ps = buildProfileSwitch(profileStore, profile.profileName, durationInMinutes, percentage, 0, dateUtil.now())
val validity = ProfileSealed.PS(ps).isValid(
resourceHelper.gs(info.nightscout.androidaps.automation.R.string.careportal_profileswitch),
activePlugin.activePump,
config,
resourceHelper,
rxBus,
hardLimits
)
var returnValue = true
if (validity.isValid) {
repository.runTransactionForResult(InsertOrUpdateProfileSwitch(ps))
.doOnError {
aapsLogger.error(LTag.DATABASE, "Error while saving ProfileSwitch", it)
returnValue = false
}
.blockingGet()
.also { result ->
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted ProfileSwitch $it") }
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated ProfileSwitch $it") }
}
} else returnValue = false
return returnValue
}
}

View file

@ -45,6 +45,7 @@ class ObjectivesPlugin @Inject constructor(
var objectives: MutableList<Objective> = ArrayList()
companion object {
const val FIRST_OBJECTIVE = 0
@Suppress("unused") const val USAGE_OBJECTIVE = 1
@Suppress("unused") const val EXAM_OBJECTIVE = 2
@ -53,40 +54,17 @@ class ObjectivesPlugin @Inject constructor(
const val MAXIOB_ZERO_CL_OBJECTIVE = 5
@Suppress("unused") const val MAXIOB_OBJECTIVE = 6
const val AUTOSENS_OBJECTIVE = 7
const val AMA_OBJECTIVE = 8
const val SMB_OBJECTIVE = 9
const val AUTO_OBJECTIVE = 10
const val SMB_OBJECTIVE = 8
const val AUTO_OBJECTIVE = 9
}
public override fun onStart() {
super.onStart()
convertSP()
setupObjectives()
}
override fun specialEnableCondition(): Boolean {
return activePlugin.activePump.pumpDescription.isTempBasalCapable
}
// convert 2.3 SP version
private fun convertSP() {
doConvertSP(0, "config")
doConvertSP(1, "openloop")
doConvertSP(2, "maxbasal")
doConvertSP(3, "maxiobzero")
doConvertSP(4, "maxiob")
doConvertSP(5, "autosens")
doConvertSP(6, "ama")
doConvertSP(7, "smb")
}
private fun doConvertSP(number: Int, name: String) {
if (!sp.contains("Objectives_" + name + "_started")) {
sp.putLong("Objectives_" + name + "_started", sp.getLong("Objectives" + number + "started", 0L))
sp.putLong("Objectives_" + name + "_accomplished", sp.getLong("Objectives" + number + "accomplished", 0L))
}
// TODO: we can remove Objectives1accomplished sometimes later
}
override fun specialEnableCondition(): Boolean =
activePlugin.activePump.pumpDescription.isTempBasalCapable
private fun setupObjectives() {
objectives.clear()
@ -98,9 +76,9 @@ class ObjectivesPlugin @Inject constructor(
objectives.add(Objective5(injector))
objectives.add(Objective6(injector))
objectives.add(Objective7(injector))
objectives.add(Objective8(injector))
objectives.add(Objective9(injector))
objectives.add(Objective10(injector))
// edit companion object if you remove/add Objective
}
fun reset() {
@ -120,12 +98,11 @@ class ObjectivesPlugin @Inject constructor(
sp.putBoolean(R.string.key_objectiveusescale, false)
}
@kotlin.ExperimentalStdlibApi
fun completeObjectives(activity: FragmentActivity, request: String) {
val requestCode = sp.getString(R.string.key_objectives_request_code, "")
var url = sp.getString(R.string.key_nsclientinternal_url, "").lowercase(Locale.getDefault())
if (!url.endsWith("/")) url = "$url/"
@Suppress("DEPRECATION") val hashNS = Hashing.sha1().hashString(url + BuildConfig.APPLICATION_ID + "/" + requestCode, Charsets.UTF_8).toString()
@Suppress("DEPRECATION", "UnstableApiUsage") val hashNS = Hashing.sha1().hashString(url + BuildConfig.APPLICATION_ID + "/" + requestCode, Charsets.UTF_8).toString()
if (request.equals(hashNS.substring(0, 10), ignoreCase = true)) {
sp.putLong("Objectives_" + "openloop" + "_started", dateUtil.now())
sp.putLong("Objectives_" + "openloop" + "_accomplished", dateUtil.now())
@ -137,8 +114,6 @@ class ObjectivesPlugin @Inject constructor(
sp.putLong("Objectives_" + "maxiob" + "_accomplished", dateUtil.now())
sp.putLong("Objectives_" + "autosens" + "_started", dateUtil.now())
sp.putLong("Objectives_" + "autosens" + "_accomplished", dateUtil.now())
sp.putLong("Objectives_" + "ama" + "_started", dateUtil.now())
sp.putLong("Objectives_" + "ama" + "_accomplished", dateUtil.now())
sp.putLong("Objectives_" + "smb" + "_started", dateUtil.now())
sp.putLong("Objectives_" + "smb" + "_accomplished", dateUtil.now())
sp.putLong("Objectives_" + "auto" + "_started", dateUtil.now())
@ -164,49 +139,43 @@ class ObjectivesPlugin @Inject constructor(
*/
override fun isLoopInvocationAllowed(value: Constraint<Boolean>): Constraint<Boolean> {
if (!objectives[FIRST_OBJECTIVE].isStarted)
value.set(aapsLogger, false, String.format(resourceHelper.gs(R.string.objectivenotstarted), FIRST_OBJECTIVE + 1), this)
value.set(aapsLogger, false, resourceHelper.gs(R.string.objectivenotstarted, FIRST_OBJECTIVE + 1), this)
return value
}
override fun isLgsAllowed(value: Constraint<Boolean>): Constraint<Boolean> {
if (!objectives[MAXBASAL_OBJECTIVE].isStarted)
value.set(aapsLogger, false, String.format(resourceHelper.gs(R.string.objectivenotstarted), MAXBASAL_OBJECTIVE + 1), this)
value.set(aapsLogger, false, resourceHelper.gs(R.string.objectivenotstarted, MAXBASAL_OBJECTIVE + 1), this)
return value
}
override fun isClosedLoopAllowed(value: Constraint<Boolean>): Constraint<Boolean> {
if (!objectives[MAXIOB_ZERO_CL_OBJECTIVE].isStarted)
value.set(aapsLogger, false, String.format(resourceHelper.gs(R.string.objectivenotstarted), MAXIOB_ZERO_CL_OBJECTIVE + 1), this)
value.set(aapsLogger, false, resourceHelper.gs(R.string.objectivenotstarted, MAXIOB_ZERO_CL_OBJECTIVE + 1), this)
return value
}
override fun isAutosensModeEnabled(value: Constraint<Boolean>): Constraint<Boolean> {
if (!objectives[AUTOSENS_OBJECTIVE].isStarted)
value.set(aapsLogger, false, String.format(resourceHelper.gs(R.string.objectivenotstarted), AUTOSENS_OBJECTIVE + 1), this)
return value
}
override fun isAMAModeEnabled(value: Constraint<Boolean>): Constraint<Boolean> {
if (!objectives[AMA_OBJECTIVE].isStarted)
value.set(aapsLogger, false, String.format(resourceHelper.gs(R.string.objectivenotstarted), AMA_OBJECTIVE + 1), this)
value.set(aapsLogger, false, resourceHelper.gs(R.string.objectivenotstarted, AUTOSENS_OBJECTIVE + 1), this)
return value
}
override fun isSMBModeEnabled(value: Constraint<Boolean>): Constraint<Boolean> {
if (!objectives[SMB_OBJECTIVE].isStarted)
value.set(aapsLogger, false, String.format(resourceHelper.gs(R.string.objectivenotstarted), SMB_OBJECTIVE + 1), this)
value.set(aapsLogger, false, resourceHelper.gs(R.string.objectivenotstarted, SMB_OBJECTIVE + 1), this)
return value
}
override fun applyMaxIOBConstraints(maxIob: Constraint<Double>): Constraint<Double> {
if (objectives[MAXIOB_ZERO_CL_OBJECTIVE].isStarted && !objectives[MAXIOB_ZERO_CL_OBJECTIVE].isAccomplished)
maxIob.set(aapsLogger, 0.0, String.format(resourceHelper.gs(R.string.objectivenotfinished), MAXIOB_ZERO_CL_OBJECTIVE + 1), this)
maxIob.set(aapsLogger, 0.0, resourceHelper.gs(R.string.objectivenotfinished, MAXIOB_ZERO_CL_OBJECTIVE + 1), this)
return maxIob
}
override fun isAutomationEnabled(value: Constraint<Boolean>): Constraint<Boolean> {
if (!objectives[AUTO_OBJECTIVE].isStarted)
value.set(aapsLogger, false, String.format(resourceHelper.gs(R.string.objectivenotstarted), AUTO_OBJECTIVE + 1), this)
value.set(aapsLogger, false, resourceHelper.gs(R.string.objectivenotstarted, AUTO_OBJECTIVE + 1), this)
return value
}
}

View file

@ -29,7 +29,6 @@ class Objective3 @Inject constructor(injector: HasAndroidInjector) : Objective(i
override fun specialActionEnabled(): Boolean =
nsClientPlugin.nsClientService?.isConnected == true && nsClientPlugin.nsClientService?.hasWriteAuth == true
@kotlin.ExperimentalStdlibApi
override fun specialAction(activity: FragmentActivity, input: String) {
objectivesPlugin.completeObjectives(activity, input)
}

View file

@ -1,12 +0,0 @@
package info.nightscout.androidaps.plugins.constraints.objectives.objectives
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.utils.T
class Objective8(injector: HasAndroidInjector) : Objective(injector, "ama", R.string.objectives_ama_objective, 0) {
init {
tasks.add(MinimumDurationTask(this, T.days(28).msecs()))
}
}

View file

@ -3,6 +3,7 @@ package info.nightscout.androidaps.plugins.constraints.safety
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.R
import info.nightscout.androidaps.extensions.*
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger
@ -20,6 +21,7 @@ import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
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
import kotlin.math.floor
@ -49,7 +51,7 @@ class SafetyPlugin @Inject constructor(
.pluginName(R.string.safety)
.preferencesId(R.xml.pref_safety),
aapsLogger, resourceHelper, injector
), Constraints {
), Constraints, Safety {
/**
* Constraints interface
@ -186,11 +188,23 @@ class SafetyPlugin @Inject constructor(
override fun applyMaxIOBConstraints(maxIob: Constraint<Double>): Constraint<Double> {
val apsMode = sp.getString(R.string.key_aps_mode, "open")
val maxIobPref: Double = if (openAPSSMBPlugin.isEnabled(PluginType.APS)) sp.getDouble(R.string.key_openapssmb_max_iob, 3.0) else sp.getDouble(R.string.key_openapsma_max_iob, 1.5)
val maxIobPref: Double = if (openAPSSMBPlugin.isEnabled()) sp.getDouble(R.string.key_openapssmb_max_iob, 3.0) else sp.getDouble(R.string.key_openapsma_max_iob, 1.5)
maxIob.setIfSmaller(aapsLogger, maxIobPref, String.format(resourceHelper.gs(R.string.limitingiob), maxIobPref, resourceHelper.gs(R.string.maxvalueinpreferences)), this)
if (openAPSAMAPlugin.isEnabled(PluginType.APS)) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobAMA(), String.format(resourceHelper.gs(R.string.limitingiob), hardLimits.maxIobAMA(), resourceHelper.gs(R.string.hardlimit)), this)
if (openAPSSMBPlugin.isEnabled(PluginType.APS)) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobSMB(), String.format(resourceHelper.gs(R.string.limitingiob), hardLimits.maxIobSMB(), resourceHelper.gs(R.string.hardlimit)), this)
if (openAPSAMAPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobAMA(), String.format(resourceHelper.gs(R.string.limitingiob), hardLimits.maxIobAMA(), resourceHelper.gs(R.string.hardlimit)), this)
if (openAPSSMBPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobSMB(), String.format(resourceHelper.gs(R.string.limitingiob), hardLimits.maxIobSMB(), resourceHelper.gs(R.string.hardlimit)), this)
if (apsMode == "lgs") maxIob.setIfSmaller(aapsLogger, HardLimits.MAX_IOB_LGS, String.format(resourceHelper.gs(R.string.limitingiob), HardLimits.MAX_IOB_LGS, resourceHelper.gs(R.string.lowglucosesuspend)), this)
return maxIob
}
override fun configuration(): JSONObject =
JSONObject()
.putString(R.string.key_age, sp, resourceHelper)
.putDouble(R.string.key_treatmentssafety_maxbolus, sp, resourceHelper)
.putDouble(R.string.key_treatmentssafety_maxcarbs, sp, resourceHelper)
override fun applyConfiguration(configuration: JSONObject) {
configuration.storeString(R.string.key_age, sp, resourceHelper)
configuration.storeDouble(R.string.key_treatmentssafety_maxbolus, sp, resourceHelper)
configuration.storeDouble(R.string.key_treatmentssafety_maxcarbs, sp, resourceHelper)
}
}

View file

@ -152,7 +152,6 @@ class SignatureVerifierPlugin @Inject constructor(
return sb.toString()
}
@kotlin.ExperimentalStdlibApi
fun singleCharUnMap(shortHash: String): String {
val array = ByteArray(shortHash.length)
val sb = StringBuilder()

View file

@ -5,6 +5,7 @@ import android.os.StatFs
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.androidaps.interfaces.Constraint
import info.nightscout.androidaps.interfaces.Constraints
import info.nightscout.androidaps.interfaces.PluginBase
@ -20,8 +21,9 @@ import info.nightscout.androidaps.utils.resources.ResourceHelper
import javax.inject.Inject
import javax.inject.Singleton
@OpenForTesting
@Singleton
open class StorageConstraintPlugin @Inject constructor(
class StorageConstraintPlugin @Inject constructor(
injector: HasAndroidInjector,
aapsLogger: AAPSLogger,
resourceHelper: ResourceHelper,
@ -48,7 +50,7 @@ open class StorageConstraintPlugin @Inject constructor(
return value
}
open fun availableInternalMemorySize(): Long {
fun availableInternalMemorySize(): Long {
val path = Environment.getDataDirectory()
val stat = StatFs(path.path)
val blockSize = stat.blockSizeLong

View file

@ -28,7 +28,8 @@ import info.nightscout.androidaps.events.EventTherapyEventChange
import info.nightscout.androidaps.extensions.toStringMedium
import info.nightscout.androidaps.extensions.toStringShort
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity
import info.nightscout.androidaps.activities.HistoryBrowseActivity
import info.nightscout.androidaps.diaconn.DiaconnG8Plugin
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.CommandQueueProvider
import info.nightscout.androidaps.interfaces.Config
@ -114,7 +115,10 @@ class ActionsFragment : DaggerFragment() {
savedInstanceState: Bundle?): View? {
//check screen width
dm = DisplayMetrics()
activity?.windowManager?.defaultDisplay?.getMetrics(dm)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R)
activity?.display?.getRealMetrics(dm)
else
@Suppress("DEPRECATION") activity?.windowManager?.defaultDisplay?.getMetrics(dm)
val screenWidth = dm.widthPixels
val screenHeight = dm.heightPixels
@ -152,7 +156,7 @@ class ActionsFragment : DaggerFragment() {
pbLevelLabel = view.findViewById(R.id.pb_level_label)
profileSwitch?.setOnClickListener {
ProfileSwitchDialog().show(childFragmentManager, "Actions")
ProfileSwitchDialog().show(childFragmentManager, "ProfileSwitchDialog")
}
tempTarget?.setOnClickListener {
TempTargetDialog().show(childFragmentManager, "Actions")
@ -304,7 +308,11 @@ class ActionsFragment : DaggerFragment() {
val activeBgSource = activePlugin.activeBgSource
historyBrowser?.visibility = (profile != null).toVisibility()
fill?.visibility = (pump.pumpDescription.isRefillingCapable && pump.isInitialized() && !pump.isSuspended()).toVisibility()
pumpBatteryChange?.visibility = (pump.pumpDescription.isBatteryReplaceable || (pump is OmnipodErosPumpPlugin && pump.isUseRileyLinkBatteryLevel && pump.isBatteryChangeLoggingEnabled)).toVisibility()
if(pump is DiaconnG8Plugin) {
pumpBatteryChange?.visibility = (pump.pumpDescription.isBatteryReplaceable && !pump.isBatteryChangeLoggingEnabled()).toVisibility()
} else {
pumpBatteryChange?.visibility = (pump.pumpDescription.isBatteryReplaceable || (pump is OmnipodErosPumpPlugin && pump.isUseRileyLinkBatteryLevel && pump.isBatteryChangeLoggingEnabled)).toVisibility()
}
tempTarget?.visibility = (profile != null && config.APS).toVisibility()
tddStats?.visibility = pump.pumpDescription.supportsTDDs.toVisibility()

View file

@ -67,7 +67,6 @@ class FoodFragment : DaggerFragment() {
return binding.root
}
@kotlin.ExperimentalStdlibApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -129,7 +128,6 @@ class FoodFragment : DaggerFragment() {
}
@Synchronized
@kotlin.ExperimentalStdlibApi
override fun onResume() {
super.onResume()
disposable.add(rxBus
@ -141,7 +139,6 @@ class FoodFragment : DaggerFragment() {
swapAdapter()
}
@kotlin.ExperimentalStdlibApi
private fun swapAdapter() {
disposable += repository
.getFoodData()
@ -202,7 +199,6 @@ class FoodFragment : DaggerFragment() {
}
}
@kotlin.ExperimentalStdlibApi
private fun filterData() {
val textFilter = binding.filter.text.toString()
val categoryFilter = binding.category.selectedItem?.toString()

View file

@ -363,7 +363,7 @@ class ImportExportPrefsImpl @Inject constructor(
override fun exportUserEntriesCsv(activity: FragmentActivity, singleEntries: Single<List<UserEntry>>) {
val entries = singleEntries.blockingGet()
prefFileList.ensureExportDirExists()
val newFile = prefFileList.newExportXmlFile()
val newFile = prefFileList.newExportCsvFile()
try {
classicPrefsFormat.saveCsv(newFile, entries)

View file

@ -1,6 +1,7 @@
package info.nightscout.androidaps.plugins.general.maintenance
import ch.qos.logback.classic.LoggerContext
import info.nightscout.androidaps.annotations.OpenForTesting
import org.slf4j.LoggerFactory
import javax.inject.Inject
import javax.inject.Singleton
@ -8,6 +9,7 @@ import javax.inject.Singleton
/**
* This class provides several methods for log-handling (eg. sending logs as emails).
*/
@OpenForTesting
@Singleton
class LoggerUtils @Inject constructor() {

View file

@ -16,11 +16,13 @@ import info.nightscout.androidaps.events.EventNewBG
import info.nightscout.androidaps.insight.database.InsightDatabase
import info.nightscout.androidaps.interfaces.DataSyncSelector
import info.nightscout.androidaps.interfaces.ImportExportPrefs
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.PumpSync
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.maintenance.activities.LogSettingActivity
import info.nightscout.androidaps.plugins.general.overview.OverviewData
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper
@ -44,6 +46,8 @@ class MaintenanceFragment : DaggerFragment() {
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var dataSyncSelector: DataSyncSelector
@Inject lateinit var pumpSync: PumpSync
@Inject lateinit var iobCobCalculator: IobCobCalculator
@Inject lateinit var overviewData: OverviewData
private val compositeDisposable = CompositeDisposable()
@ -63,7 +67,9 @@ class MaintenanceFragment : DaggerFragment() {
binding.logSend.setOnClickListener { maintenancePlugin.sendLogs() }
binding.logDelete.setOnClickListener {
uel.log(Action.DELETE_LOGS, Sources.Maintenance)
maintenancePlugin.deleteLogs()
Thread {
maintenancePlugin.deleteLogs(5)
}.start()
}
binding.navResetdb.setOnClickListener {
activity?.let { activity ->
@ -75,6 +81,9 @@ class MaintenanceFragment : DaggerFragment() {
insightDatabase.clearAllTables()
dataSyncSelector.resetToNextFullSync()
pumpSync.connectNewPump()
overviewData.reset()
iobCobCalculator.ads.reset()
iobCobCalculator.clearCache()
}
.subscribeOn(aapsSchedulers.io)
.observeOn(aapsSchedulers.main)

View file

@ -8,8 +8,8 @@ import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.BuildConfig
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.R
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
@ -35,17 +35,19 @@ class MaintenancePlugin @Inject constructor(
aapsLogger: AAPSLogger,
private val buildHelper: BuildHelper,
private val config: Config,
private val fileListProvider: PrefFileListProvider,
private val loggerUtils: LoggerUtils
) : PluginBase(PluginDescription()
.mainType(PluginType.GENERAL)
.fragmentClass(MaintenanceFragment::class.java.name)
.alwaysVisible(false)
.alwaysEnabled(true)
.pluginIcon(R.drawable.ic_maintenance)
.pluginName(R.string.maintenance)
.shortName(R.string.maintenance_shortname)
.preferencesId(R.xml.pref_maintenance)
.description(R.string.description_maintenance),
) : PluginBase(
PluginDescription()
.mainType(PluginType.GENERAL)
.fragmentClass(MaintenanceFragment::class.java.name)
.alwaysVisible(false)
.alwaysEnabled(true)
.pluginIcon(R.drawable.ic_maintenance)
.pluginName(R.string.maintenance)
.shortName(R.string.maintenance_shortname)
.preferencesId(R.xml.pref_maintenance)
.description(R.string.description_maintenance),
aapsLogger, resourceHelper, injector
) {
@ -53,11 +55,12 @@ class MaintenancePlugin @Inject constructor(
val recipient = sp.getString(R.string.key_maintenance_logs_email, "logs@androidaps.org")
val amount = sp.getInt(R.string.key_maintenance_logs_amount, 2)
val logs = getLogFiles(amount)
val zipDir = context.getExternalFilesDir("exports")
val zipDir = fileListProvider.ensureTempDirExists()
val zipFile = File(zipDir, constructName())
aapsLogger.debug("zipFile: ${zipFile.absolutePath}")
val zip = zipLogs(zipFile, logs)
val attachmentUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", zip)
val attachmentUri =
FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", zip)
val emailIntent: Intent = this.sendMail(attachmentUri, recipient, "Log Export")
aapsLogger.debug("sending emailIntent")
context.startActivity(emailIntent)
@ -65,14 +68,15 @@ class MaintenancePlugin @Inject constructor(
//todo replace this with a call on startup of the application, specifically to remove
// unnecessary garbage from the log exports
fun deleteLogs() {
fun deleteLogs(keep: Int) {
val logDir = File(loggerUtils.logDirectory)
val files = logDir.listFiles { _: File?, name: String ->
(name.startsWith("AndroidAPS") && name.endsWith(".zip"))
}
Arrays.sort(files) { f1: File, f2: File -> f1.name.compareTo(f2.name) }
if (files == null || files.isEmpty()) return
Arrays.sort(files) { f1: File, f2: File -> f2.name.compareTo(f1.name) }
var delFiles = listOf(*files)
val amount = sp.getInt(R.string.key_logshipper_amount, 2)
val amount = sp.getInt(R.string.key_logshipper_amount, keep)
val keepIndex = amount - 1
if (keepIndex < delFiles.size) {
delFiles = delFiles.subList(keepIndex, delFiles.size)
@ -80,11 +84,10 @@ class MaintenancePlugin @Inject constructor(
file.delete()
}
}
val exportDir = File(loggerUtils.logDirectory, "exports")
val exportDir = fileListProvider.ensureTempDirExists()
if (exportDir.exists()) {
val expFiles = exportDir.listFiles()
for (file in expFiles) {
file.delete()
exportDir.listFiles()?.let { expFiles ->
for (file in expFiles) file.delete()
}
exportDir.delete()
}
@ -106,7 +109,7 @@ class MaintenancePlugin @Inject constructor(
(name.startsWith("AndroidAPS")
&& (name.endsWith(".log")
|| name.endsWith(".zip") && !name.endsWith(loggerUtils.suffix)))
}
} ?: emptyArray()
Arrays.sort(files) { f1: File, f2: File -> f2.name.compareTo(f1.name) }
val result = listOf(*files)
var toIndex = amount
@ -192,7 +195,12 @@ class MaintenancePlugin @Inject constructor(
*
* @return
*/
private fun sendMail(attachmentUri: Uri, recipient: String, subject: String, body: String): Intent {
private fun sendMail(
attachmentUri: Uri,
recipient: String,
subject: String,
body: String
): Intent {
aapsLogger.debug("sending email to $recipient with subject $subject")
val emailIntent = Intent(Intent.ACTION_SEND)
emailIntent.type = "text/plain"
@ -207,8 +215,9 @@ class MaintenancePlugin @Inject constructor(
override fun preprocessPreferences(preferenceFragment: PreferenceFragmentCompat) {
super.preprocessPreferences(preferenceFragment)
val encryptSwitch = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_maintenance_encrypt_exported_prefs)) as SwitchPreference?
?: return
val encryptSwitch =
preferenceFragment.findPreference(resourceHelper.gs(R.string.key_maintenance_encrypt_exported_prefs)) as SwitchPreference?
?: return
encryptSwitch.isVisible = buildHelper.isEngineeringMode()
encryptSwitch.isEnabled = buildHelper.isEngineeringMode()
}

View file

@ -34,11 +34,13 @@ class DataSyncSelectorImplementation @Inject constructor(
processChangedTemporaryBasalsCompat()
processChangedExtendedBolusesCompat()
processChangedProfileSwitchesCompat()
processChangedEffectiveProfileSwitchesCompat()
processChangedGlucoseValuesCompat()
processChangedTempTargetsCompat()
processChangedFoodsCompat()
processChangedTherapyEventsCompat()
processChangedDeviceStatusesCompat()
processChangedOfflineEventsCompat()
processChangedProfileStore()
}
}
@ -55,6 +57,8 @@ class DataSyncSelectorImplementation @Inject constructor(
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_profile_switch_last_synced_id)
sp.remove(R.string.key_ns_effective_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)
}
@ -76,8 +80,8 @@ class DataSyncSelectorImplementation @Inject constructor(
}
}
private var lastBolusId = -1L
private var lastBolusTime = -1L
@Volatile private var lastBolusId = -1L
@Volatile private var lastBolusTime = -1L
override fun processChangedBolusesCompat(): Boolean {
val lastDbIdWrapped = appRepository.getLastBolusIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
@ -119,8 +123,8 @@ class DataSyncSelectorImplementation @Inject constructor(
}
}
private var lastCarbsId = -1L
private var lastCarbsTime = -1L
@Volatile private var lastCarbsId = -1L
@Volatile private var lastCarbsTime = -1L
override fun processChangedCarbsCompat(): Boolean {
val lastDbIdWrapped = appRepository.getLastCarbsIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
@ -162,8 +166,8 @@ class DataSyncSelectorImplementation @Inject constructor(
}
}
private var lastBcrId = -1L
private var lastBcrTime = -1L
@Volatile private var lastBcrId = -1L
@Volatile private var lastBcrTime = -1L
override fun processChangedBolusCalculatorResultsCompat(): Boolean {
val lastDbIdWrapped = appRepository.getLastBolusCalculatorResultIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
@ -205,8 +209,8 @@ class DataSyncSelectorImplementation @Inject constructor(
}
}
private var lastTtId = -1L
private var lastTtTime = -1L
@Volatile private var lastTtId = -1L
@Volatile private var lastTtTime = -1L
override fun processChangedTempTargetsCompat(): Boolean {
val lastDbIdWrapped = appRepository.getLastTempTargetIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
@ -248,8 +252,8 @@ class DataSyncSelectorImplementation @Inject constructor(
}
}
private var lastFoodId = -1L
private var lastFoodTime = -1L
@Volatile private var lastFoodId = -1L
@Volatile private var lastFoodTime = -1L
override fun processChangedFoodsCompat(): Boolean {
val lastDbIdWrapped = appRepository.getLastFoodIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
@ -291,8 +295,8 @@ class DataSyncSelectorImplementation @Inject constructor(
}
}
private var lastGvId = -1L
private var lastGvTime = -1L
@Volatile private var lastGvId = -1L
@Volatile private var lastGvTime = -1L
override fun processChangedGlucoseValuesCompat(): Boolean {
val lastDbIdWrapped = appRepository.getLastGlucoseValueIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
@ -340,8 +344,8 @@ class DataSyncSelectorImplementation @Inject constructor(
}
}
private var lastTeId = -1L
private var lastTeTime = -1L
@Volatile private var lastTeId = -1L
@Volatile private var lastTeTime = -1L
override fun processChangedTherapyEventsCompat(): Boolean {
val lastDbIdWrapped = appRepository.getLastTherapyEventIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
@ -358,10 +362,10 @@ class DataSyncSelectorImplementation @Inject constructor(
when {
// without nsId = create new
te.first.interfaceIDs.nightscoutId == null ->
nsClientPlugin.nsClientService?.dbAdd("treatments", te.first.toJson(true), DataSyncSelector.PairTherapyEvent(te.first, te.second), "$startId/$lastDbId")
nsClientPlugin.nsClientService?.dbAdd("treatments", te.first.toJson(true, dateUtil), DataSyncSelector.PairTherapyEvent(te.first, te.second), "$startId/$lastDbId")
// nsId = update
te.first.interfaceIDs.nightscoutId != null ->
nsClientPlugin.nsClientService?.dbUpdate("treatments", te.first.interfaceIDs.nightscoutId, te.first.toJson(false), DataSyncSelector.PairTherapyEvent(te.first, te.second), "$startId/$lastDbId")
nsClientPlugin.nsClientService?.dbUpdate("treatments", te.first.interfaceIDs.nightscoutId, te.first.toJson(false, dateUtil), DataSyncSelector.PairTherapyEvent(te.first, te.second), "$startId/$lastDbId")
}
return true
}
@ -382,8 +386,8 @@ class DataSyncSelectorImplementation @Inject constructor(
}
}
private var lastDsId = -1L
private var lastDsTime = -1L
@Volatile private var lastDsId = -1L
@Volatile private var lastDsTime = -1L
override fun processChangedDeviceStatusesCompat(): Boolean {
val lastDbIdWrapped = appRepository.getLastDeviceStatusIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
@ -424,8 +428,8 @@ class DataSyncSelectorImplementation @Inject constructor(
}
}
private var lastTbrId = -1L
private var lastTbrTime = -1L
@Volatile private var lastTbrId = -1L
@Volatile private var lastTbrTime = -1L
override fun processChangedTemporaryBasalsCompat(): Boolean {
val useAbsolute = sp.getBoolean(R.string.key_ns_sync_use_absolute, false)
val lastDbIdWrapped = appRepository.getLastTemporaryBasalIdWrapped().blockingGet()
@ -475,8 +479,8 @@ class DataSyncSelectorImplementation @Inject constructor(
}
}
private var lastEbId = -1L
private var lastEbTime = -1L
@Volatile private var lastEbId = -1L
@Volatile private var lastEbTime = -1L
override fun processChangedExtendedBolusesCompat(): Boolean {
val useAbsolute = sp.getBoolean(R.string.key_ns_sync_use_absolute, false)
val lastDbIdWrapped = appRepository.getLastExtendedBolusIdWrapped().blockingGet()
@ -525,8 +529,8 @@ class DataSyncSelectorImplementation @Inject constructor(
}
}
private var lastPsId = -1L
private var lastPsTime = -1L
@Volatile private var lastPsId = -1L
@Volatile private var lastPsTime = -1L
override fun processChangedProfileSwitchesCompat(): Boolean {
val lastDbIdWrapped = appRepository.getLastProfileSwitchIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
@ -553,6 +557,91 @@ class DataSyncSelectorImplementation @Inject constructor(
return false
}
override fun confirmLastEffectiveProfileSwitchIdIfGreater(lastSynced: Long) {
if (lastSynced > sp.getLong(R.string.key_ns_effective_profile_switch_last_synced_id, 0)) {
aapsLogger.debug(LTag.NSCLIENT, "Setting EffectiveProfileSwitch data sync from $lastSynced")
sp.putLong(R.string.key_ns_effective_profile_switch_last_synced_id, lastSynced)
}
}
override fun changedEffectiveProfileSwitch(): List<EffectiveProfileSwitch> {
val startId = sp.getLong(R.string.key_ns_effective_profile_switch_last_synced_id, 0)
return appRepository.getModifiedEffectiveProfileSwitchDataFromId(startId).blockingGet().also {
aapsLogger.debug(LTag.NSCLIENT, "Loading EffectiveProfileSwitch data for sync from $startId. Records ${it.size}")
}
}
@Volatile private var lastEpsId = -1L
@Volatile private var lastEpsTime = -1L
override fun processChangedEffectiveProfileSwitchesCompat(): Boolean {
val lastDbIdWrapped = appRepository.getLastEffectiveProfileSwitchIdWrapped().blockingGet()
val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L
var startId = sp.getLong(R.string.key_ns_effective_profile_switch_last_synced_id, 0)
if (startId > lastDbId) {
sp.putLong(R.string.key_ns_effective_profile_switch_last_synced_id, 0)
startId = 0
}
if (startId == lastEpsId && dateUtil.now() - lastEpsTime < 5000) return false
lastEpsId = startId
lastEpsTime = dateUtil.now()
appRepository.getNextSyncElementEffectiveProfileSwitch(startId).blockingGet()?.let { ps ->
aapsLogger.info(LTag.DATABASE, "Loading EffectiveProfileSwitch data Start: $startId ID: ${ps.first.id} HistoryID: ${ps.second} ")
when {
// without nsId = create new
ps.first.interfaceIDs.nightscoutId == null ->
nsClientPlugin.nsClientService?.dbAdd("treatments", ps.first.toJson(true, dateUtil), DataSyncSelector.PairEffectiveProfileSwitch(ps.first, ps.second), "$startId/$lastDbId")
// with nsId = update
ps.first.interfaceIDs.nightscoutId != null ->
nsClientPlugin.nsClientService?.dbUpdate("treatments", ps.first.interfaceIDs.nightscoutId, ps.first.toJson(false, dateUtil), DataSyncSelector.PairEffectiveProfileSwitch(ps.first, ps.second), "$startId/$lastDbId")
}
return true
}
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}")
}
}
@Volatile private var lastOeId = -1L
@Volatile 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)
}
@ -561,9 +650,9 @@ class DataSyncSelectorImplementation @Inject constructor(
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
localProfilePlugin.createProfileStore()
val profileJson = localProfilePlugin.profile?.data ?: return
if (lastChange > lastSync)
if (lastChange > lastSync) {
val profileJson = localProfilePlugin.profile?.data ?: return
nsClientPlugin.nsClientService?.dbAdd("profile", profileJson, DataSyncSelector.PairProfileStore(profileJson, dateUtil.now()), "")
}
}
}

View file

@ -228,6 +228,25 @@ class NSClientAddAckWorker(
dataSyncSelector.processChangedProfileSwitchesCompat()
}
is PairEffectiveProfileSwitch -> {
val pair = ack.originalObject
pair.value.interfaceIDs.nightscoutId = ack.id
repository.runTransactionForResult(UpdateNsIdEffectiveProfileSwitchTransaction(pair.value))
.doOnError { error ->
aapsLogger.error(LTag.DATABASE, "Updated ns id of EffectiveProfileSwitch 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 EffectiveProfileSwitch " + pair.value)
dataSyncSelector.confirmLastEffectiveProfileSwitchIdIfGreater(pair.updateRecordId)
}
.blockingGet()
rxBus.send(EventNSClientNewLog("DBADD", "Acked EffectiveProfileSwitch " + pair.value.interfaceIDs.nightscoutId))
// Send new if waiting
dataSyncSelector.processChangedEffectiveProfileSwitchesCompat()
}
is DeviceStatus -> {
val deviceStatus = ack.originalObject
deviceStatus.interfaceIDs.nightscoutId = ack.id
@ -251,6 +270,26 @@ class NSClientAddAckWorker(
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
}

View file

@ -26,7 +26,6 @@ import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.JsonHelper
import info.nightscout.androidaps.utils.JsonHelper.safeGetLong
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import java.util.concurrent.TimeUnit
@ -186,6 +185,32 @@ class NSClientAddUpdateWorker(
}
} ?: aapsLogger.error("Error parsing TT json $json")
}
eventType == TherapyEvent.Type.NOTE.text && json.isEffectiveProfileSwitch() -> // replace this by new Type when available in NS
if (sp.getBoolean(R.string.key_ns_receive_profile_switch, false) && buildHelper.isEngineeringMode() || config.NSCLIENT) {
effectiveProfileSwitchFromJson(json, dateUtil)?.let { effectiveProfileSwitch ->
repository.runTransactionForResult(SyncNsEffectiveProfileSwitchTransaction(effectiveProfileSwitch, invalidateByNsOnly = false))
.doOnError {
aapsLogger.error(LTag.DATABASE, "Error while saving EffectiveProfileSwitch", it)
ret = Result.failure(workDataOf("Error" to it.toString()))
}
.blockingGet()
.also { result ->
result.inserted.forEach {
uel.log(Action.PROFILE_SWITCH, Sources.NSClient,
ValueWithUnit.Timestamp(it.timestamp))
aapsLogger.debug(LTag.DATABASE, "Inserted EffectiveProfileSwitch $it")
}
result.invalidated.forEach {
uel.log(Action.PROFILE_SWITCH_REMOVED, Sources.NSClient,
ValueWithUnit.Timestamp(it.timestamp))
aapsLogger.debug(LTag.DATABASE, "Invalidated EffectiveProfileSwitch $it")
}
result.updatedNsId.forEach {
aapsLogger.debug(LTag.DATABASE, "Updated nsId EffectiveProfileSwitch $it")
}
}
} ?: aapsLogger.error("Error parsing EffectiveProfileSwitch json $json")
}
eventType == TherapyEvent.Type.CANNULA_CHANGE.text ||
eventType == TherapyEvent.Type.INSULIN_CHANGE.text ||
eventType == TherapyEvent.Type.SENSOR_CHANGE.text ||
@ -194,7 +219,7 @@ class NSClientAddUpdateWorker(
eventType == TherapyEvent.Type.ANNOUNCEMENT.text ||
eventType == TherapyEvent.Type.QUESTION.text ||
eventType == TherapyEvent.Type.EXERCISE.text ||
eventType == TherapyEvent.Type.APS_OFFLINE.text ||
eventType == TherapyEvent.Type.NOTE.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 ->
@ -341,6 +366,43 @@ class NSClientAddUpdateWorker(
}
} ?: 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) {

View file

@ -34,7 +34,7 @@ class NSClientMbgWorker(
var ret = Result.success()
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))
?: return Result.failure(workDataOf("Error" to "missing input data"))

View file

@ -125,6 +125,24 @@ class NSClientUpdateRemoveAckWorker(
dataSyncSelector.processChangedProfileSwitchesCompat()
ret = Result.success(workDataOf("ProcessedData" to pair.toString()))
}
is PairEffectiveProfileSwitch -> {
val pair = ack.originalObject
dataSyncSelector.confirmLastEffectiveProfileSwitchIdIfGreater(pair.updateRecordId)
rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked EffectiveProfileSwitch " + ack._id))
// Send new if waiting
dataSyncSelector.processChangedEffectiveProfileSwitchesCompat()
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
}

View file

@ -16,8 +16,8 @@ class NSAddAck(
) : Event(), Ack {
var id: String? = null
@JvmField var nsClientID: String? = null
@JvmField var json: JSONObject? = null
var nsClientID: String? = null
var json: JSONObject? = null
override fun call(vararg args: Any) {
// Regular response
try {

View file

@ -3,6 +3,7 @@ package info.nightscout.androidaps.plugins.general.nsclient.data
import android.content.Context
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.R
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.androidaps.database.entities.UserEntry
import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.logging.AAPSLogger
@ -113,6 +114,7 @@ import javax.inject.Singleton
}
*/
@Suppress("SpellCheckingInspection")
@OpenForTesting
@Singleton
class NSSettingsStatus @Inject constructor(
private val aapsLogger: AAPSLogger,

View file

@ -3,6 +3,7 @@ package info.nightscout.androidaps.plugins.general.nsclient.services
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.pm.ResolveInfo
import android.os.*
import androidx.work.OneTimeWorkRequest
import com.google.common.base.Charsets
@ -16,7 +17,6 @@ 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
@ -71,7 +71,6 @@ class NSClientService : DaggerService() {
@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
@ -113,7 +112,6 @@ class NSClientService : DaggerService() {
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")
@ -199,7 +197,7 @@ class NSClientService : DaggerService() {
.build())
}
fun processAuthAck(ack: NSAuthAck) {
private fun processAuthAck(ack: NSAuthAck) {
var connectionStatus = "Authenticated ("
if (ack.read) connectionStatus += "R"
if (ack.write) connectionStatus += "W"
@ -237,7 +235,6 @@ class NSClientService : DaggerService() {
return START_STICKY
}
@kotlin.ExperimentalStdlibApi
fun initialize() {
dataCounter = 0
readPreferences()
@ -488,7 +485,7 @@ class NSClientService : DaggerService() {
val intent = Intent(Intents.ACTION_NEW_PROFILE)
intent.putExtras(bundle)
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
sendBroadcast(intent)
broadcast(intent)
}
}
}
@ -516,7 +513,7 @@ class NSClientService : DaggerService() {
val intent = Intent(Intents.ACTION_REMOVED_TREATMENT)
intent.putExtras(bundle)
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
sendBroadcast(intent)
broadcast(intent)
}
}
if (addedOrUpdatedTreatments.length() > 0) {
@ -533,7 +530,7 @@ class NSClientService : DaggerService() {
val intent = Intent(Intents.ACTION_CHANGED_TREATMENT)
intent.putExtras(bundle)
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
sendBroadcast(intent)
broadcast(intent)
}
}
}
@ -581,7 +578,7 @@ class NSClientService : DaggerService() {
val intent = Intent(Intents.ACTION_NEW_SGV)
intent.putExtras(bundle)
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
sendBroadcast(intent)
broadcast(intent)
}
}
}
@ -652,7 +649,6 @@ class NSClientService : DaggerService() {
}
}
@kotlin.ExperimentalStdlibApi
fun restart() {
destroy()
initialize()
@ -721,6 +717,16 @@ class NSClientService : DaggerService() {
return ret
}
private fun broadcast(intent: Intent) {
val receivers: List<ResolveInfo> = packageManager.queryBroadcastReceivers(intent, 0)
for (resolveInfo in receivers)
resolveInfo.activityInfo.packageName?.let {
intent.setPackage(it)
sendBroadcast(intent)
aapsLogger.debug(LTag.CORE, "Sending broadcast " + intent.action + " to: " + it)
}
}
init {
if (handler == null) {
val handlerThread = HandlerThread(NSClientService::class.java.simpleName + "Handler")

View file

@ -1,68 +0,0 @@
package info.nightscout.androidaps.plugins.general.openhumans
import android.app.Notification
import android.content.Context
import android.net.wifi.WifiManager
import androidx.core.app.NotificationCompat
import androidx.work.ForegroundInfo
import androidx.work.RxWorker
import androidx.work.WorkerParameters
import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader.Companion.NOTIFICATION_CHANNEL
import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader.Companion.UPLOAD_NOTIFICATION_ID
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import io.reactivex.Single
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class OHUploadWorker(context: Context, workerParameters: WorkerParameters)
: RxWorker(context, workerParameters) {
@Inject
lateinit var sp: SP
@Inject
lateinit var openHumansUploader: OpenHumansUploader
@Inject
lateinit var resourceHelper: ResourceHelper
@kotlin.ExperimentalStdlibApi
override fun createWork(): Single<Result> = Single.defer {
// Here we inject every time we create work
// We could build our own WorkerFactory with dagger but this will create conflicts with other Workers
// (see https://medium.com/wonderquill/how-to-pass-custom-parameters-to-rxworker-worker-using-dagger-2-f4cfbc9892ba)
// This class will be replaced with new DB
(applicationContext as MainApp).androidInjector().inject(this)
val wifiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as? WifiManager
val wifiOnly = sp.getBoolean("key_oh_wifi_only", true)
val isConnectedToWifi = wifiManager?.isWifiEnabled ?: false && wifiManager?.connectionInfo?.networkId != -1
if (!wifiOnly || (wifiOnly && isConnectedToWifi)) {
setForegroundAsync(createForegroundInfo())
openHumansUploader.uploadDataSegmentally()
.andThen(Single.just(Result.success()))
.onErrorResumeNext { Single.just(Result.retry()) }
} else {
Single.just(Result.retry())
}
}
private fun createForegroundInfo(): ForegroundInfo {
val title = resourceHelper.gs(info.nightscout.androidaps.R.string.open_humans)
val notification: Notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL)
.setContentTitle(title)
.setTicker(title)
.setContentText(resourceHelper.gs(info.nightscout.androidaps.R.string.your_phone_is_upload_data))
.setSmallIcon(info.nightscout.androidaps.R.drawable.notif_icon)
.setOngoing(true)
.setProgress(0, 0 , true)
.build()
return ForegroundInfo(UPLOAD_NOTIFICATION_ID, notification)
}
}

View file

@ -1,191 +0,0 @@
package info.nightscout.androidaps.plugins.general.openhumans
import android.annotation.SuppressLint
import android.util.Base64
import io.reactivex.Completable
import io.reactivex.Single
import io.reactivex.disposables.Disposables
import okhttp3.*
import okio.BufferedSink
import org.json.JSONArray
import org.json.JSONObject
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*
class OpenHumansAPI(
private val baseUrl: String,
clientId: String,
clientSecret: String,
private val redirectUri: String
) {
private val authHeader = "Basic " + Base64.encodeToString("$clientId:$clientSecret".toByteArray(), Base64.NO_WRAP)
private val client = OkHttpClient()
fun exchangeAuthToken(code: String): Single<OAuthTokens> = sendTokenRequest(FormBody.Builder()
.add("grant_type", "authorization_code")
.add("redirect_uri", redirectUri)
.add("code", code)
.build())
fun refreshAccessToken(refreshToken: String): Single<OAuthTokens> = sendTokenRequest(FormBody.Builder()
.add("grant_type", "refresh_token")
.add("redirect_uri", redirectUri)
.add("refresh_token", refreshToken)
.build())
private fun sendTokenRequest(body: FormBody) = Request.Builder()
.url("$baseUrl/oauth2/token/")
.addHeader("Authorization", authHeader)
.post(body)
.build()
.toSingle()
.map { response ->
response.use { _ ->
val responseBody = response.body
val jsonObject = responseBody?.let { JSONObject(it.string()) }
if (!response.isSuccessful) throw OHHttpException(response.code, response.message, jsonObject?.getString("error"))
if (jsonObject == null) throw OHHttpException(response.code, response.message, "No body")
if (!jsonObject.has("expires_in")) throw OHMissingFieldException("expires_in")
OAuthTokens(
accessToken = jsonObject.getString("access_token")
?: throw OHMissingFieldException("access_token"),
refreshToken = jsonObject.getString("refresh_token")
?: throw OHMissingFieldException("refresh_token"),
expiresAt = response.sentRequestAtMillis + jsonObject.getInt("expires_in") * 1000L
)
}
}
fun getProjectMemberId(accessToken: String): Single<String> = Request.Builder()
.url("$baseUrl/api/direct-sharing/project/exchange-member/?access_token=$accessToken")
.get()
.build()
.toSingle()
.map {
it.jsonBody.getString("project_member_id")
?: throw OHMissingFieldException("project_member_id")
}
fun prepareFileUpload(accessToken: String, fileName: String, metadata: FileMetadata): Single<PreparedUpload> = Request.Builder()
.url("$baseUrl/api/direct-sharing/project/files/upload/direct/?access_token=$accessToken")
.post(FormBody.Builder()
.add("filename", fileName)
.add("metadata", metadata.toJSON().toString())
.build())
.build()
.toSingle()
.map {
val json = it.jsonBody
PreparedUpload(
fileId = json.getString("id") ?: throw OHMissingFieldException("id"),
uploadURL = json.getString("url") ?: throw OHMissingFieldException("url")
)
}
fun uploadFile(url: String, content: ByteArray): Completable = Request.Builder()
.url(url)
.put(object : RequestBody() {
override fun contentType(): MediaType? = null
override fun contentLength() = content.size.toLong()
override fun writeTo(sink: BufferedSink) {
sink.write(content)
}
})
.build()
.toSingle()
.doOnSuccess { response ->
response.use { _ ->
if (!response.isSuccessful) throw OHHttpException(response.code, response.message, null)
}
}
.ignoreElement()
fun completeFileUpload(accessToken: String, fileId: String): Completable = Request.Builder()
.url("$baseUrl/api/direct-sharing/project/files/upload/complete/?access_token=$accessToken")
.post(FormBody.Builder()
.add("file_id", fileId)
.build())
.build()
.toSingle()
.doOnSuccess { it.jsonBody }
.ignoreElement()
private fun Request.toSingle() = Single.create<Response> {
val call = client.newCall(this)
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
it.tryOnError(e)
}
override fun onResponse(call: Call, response: Response) {
it.onSuccess(response)
}
})
it.setDisposable(Disposables.fromRunnable { call.cancel() })
}
private val Response.jsonBody
get() = use { _ ->
val jsonObject = body?.let { JSONObject(it.string()) }
?: throw OHHttpException(code, message, null)
if (!isSuccessful) throw OHHttpException(code, message, jsonObject.getString("detail"))
jsonObject
}
data class OAuthTokens(
val accessToken: String,
val refreshToken: String,
val expiresAt: Long
)
data class FileMetadata(
val tags: List<String>,
val description: String,
val md5: String? = null,
val creationDate: Long? = null,
val startDate: Long? = null,
val endDate: Long? = null
) {
fun toJSON(): JSONObject {
val jsonObject = JSONObject()
jsonObject.put("tags", JSONArray().apply { tags.forEach { put(it) } })
jsonObject.put("description", description)
jsonObject.put("md5", md5)
creationDate?.let { jsonObject.put("creation_date", iso8601DateFormatter.format(Date(it))) }
startDate?.let { jsonObject.put("start_date", iso8601DateFormatter.format(Date(it))) }
endDate?.let { jsonObject.put("end_date", iso8601DateFormatter.format(Date(it))) }
return jsonObject
}
}
data class PreparedUpload(
val fileId: String,
val uploadURL: String
)
data class OHHttpException(
val code: Int,
val meaning: String,
val detail: String?
) : RuntimeException() {
override val message: String get() = toString()
}
data class OHMissingFieldException(
val name: String
) : RuntimeException() {
override val message: String get() = toString()
}
companion object {
@SuppressLint("SimpleDateFormat")
private val iso8601DateFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
}
}

View file

@ -1,116 +0,0 @@
package info.nightscout.androidaps.plugins.general.openhumans
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.work.WorkManager
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.Event
import info.nightscout.androidaps.interfaces.DatabaseHelperInterface
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import io.reactivex.BackpressureStrategy
import io.reactivex.Single
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class OpenHumansFragment : DaggerFragment() {
private var viewsCreated = false
private var login: Button? = null
private var logout: Button? = null
private var memberId: TextView? = null
private var queueSize: TextView? = null
private var workerState: TextView? = null
private var queueSizeValue = 0L
private val compositeDisposable = CompositeDisposable()
@Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var openHumansUploader: OpenHumansUploader
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var databaseHelper: DatabaseHelperInterface
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
compositeDisposable += Single.fromCallable { databaseHelper.getOHQueueSize() }
.subscribeOn(aapsSchedulers.io)
.repeatWhen {
rxBus.toObservable(UpdateViewEvent::class.java)
.cast(Any::class.java)
.mergeWith(rxBus.toObservable(UpdateQueueEvent::class.java)
.throttleLatest(5, TimeUnit.SECONDS))
.toFlowable(BackpressureStrategy.LATEST)
}
.observeOn(aapsSchedulers.main)
.subscribe({
queueSizeValue = it
updateGUI()
}, {})
context?.applicationContext?.let { appContext ->
WorkManager.getInstance(appContext).getWorkInfosForUniqueWorkLiveData(OpenHumansUploader.WORK_NAME).observe(this, {
val workInfo = it.lastOrNull()
if (workInfo == null) {
workerState?.visibility = View.GONE
} else {
workerState?.visibility = View.VISIBLE
workerState?.text = getString(R.string.worker_state, workInfo.state.toString())
}
})
}
}
override fun onDestroy() {
compositeDisposable.clear()
super.onDestroy()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val view = inflater.inflate(R.layout.fragment_open_humans, container, false)
login = view.findViewById(R.id.login)
logout = view.findViewById(R.id.logout)
memberId = view.findViewById(R.id.member_id)
queueSize = view.findViewById(R.id.queue_size)
workerState = view.findViewById(R.id.worker_state)
login!!.setOnClickListener { startActivity(Intent(context, OpenHumansLoginActivity::class.java)) }
logout!!.setOnClickListener {
activity?.let { activity -> OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.oh_logout_confirmation)) { openHumansUploader.logout() } }
}
viewsCreated = true
updateGUI()
return view
}
override fun onDestroyView() {
viewsCreated = false
login = null
logout = null
memberId = null
queueSize = null
super.onDestroyView()
}
fun updateGUI() {
if (viewsCreated) {
queueSize!!.text = getString(R.string.queue_size, queueSizeValue)
val projectMemberId = openHumansUploader.projectMemberId
memberId!!.text = getString(R.string.project_member_id, projectMemberId
?: getString(R.string.not_logged_in))
login!!.visibility = if (projectMemberId == null) View.VISIBLE else View.GONE
logout!!.visibility = if (projectMemberId != null) View.VISIBLE else View.GONE
}
}
object UpdateViewEvent : Event()
object UpdateQueueEvent : Event()
}

View file

@ -1,138 +0,0 @@
package info.nightscout.androidaps.plugins.general.openhumans
import android.app.Dialog
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Button
import android.widget.CheckBox
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.browser.customtabs.CustomTabsIntent
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import dagger.android.support.DaggerDialogFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.NoSplashAppCompatActivity
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import io.reactivex.disposables.Disposable
import javax.inject.Inject
class OpenHumansLoginActivity : NoSplashAppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_open_humans_login)
val button = findViewById<Button>(R.id.button)
val checkbox = findViewById<CheckBox>(R.id.checkbox)
button.setOnClickListener {
if (checkbox.isChecked) {
CustomTabsIntent.Builder().build().launchUrl(this, Uri.parse(OpenHumansUploader.AUTH_URL))
} else {
Toast.makeText(this, R.string.you_need_to_accept_the_of_use_first, Toast.LENGTH_SHORT).show()
}
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
val code = intent.data?.getQueryParameter("code")
if (supportFragmentManager.fragments.size == 0 && code != null) {
ExchangeAuthTokenDialog(code).show(supportFragmentManager, "ExchangeAuthTokenDialog")
}
}
class ExchangeAuthTokenDialog : DaggerDialogFragment() {
@Inject lateinit var openHumansUploader: OpenHumansUploader
@Inject lateinit var aapsSchedulers: AapsSchedulers
private var disposable: Disposable? = null
init {
isCancelable = false
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return AlertDialog.Builder(requireActivity())
.setTitle(R.string.completing_login)
.setMessage(R.string.please_wait)
.create()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
disposable = openHumansUploader.login(arguments?.getString("authToken")!!).subscribeOn(aapsSchedulers.io).subscribe({
dismiss()
SetupDoneDialog().show(parentFragmentManager, "SetupDoneDialog")
}, {
dismiss()
ErrorDialog(it.message).show(parentFragmentManager, "ErrorDialog")
})
}
override fun onDestroy() {
disposable?.dispose()
super.onDestroy()
}
companion object {
operator fun invoke(authToken: String): ExchangeAuthTokenDialog {
val dialog = ExchangeAuthTokenDialog()
val args = Bundle()
args.putString("authToken", authToken)
dialog.arguments = args
return dialog
}
}
}
class ErrorDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val message = arguments?.getString("message")
val shownMessage = if (message == null) getString(R.string.there_was_an_error)
else "${getString(R.string.there_was_an_error)}\n\n$message"
return AlertDialog.Builder(requireActivity())
.setTitle(R.string.error)
.setMessage(shownMessage)
.setPositiveButton(R.string.close, null)
.create()
}
companion object {
operator fun invoke(message: String?): ErrorDialog {
val dialog = ErrorDialog()
val args = Bundle()
args.putString("message", message)
dialog.arguments = args
return dialog
}
}
}
class SetupDoneDialog : DialogFragment() {
init {
isCancelable = false
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return AlertDialog.Builder(requireActivity())
.setTitle(R.string.successfully_logged_in)
.setMessage(R.string.setup_will_continue_in_background)
.setCancelable(false)
.setPositiveButton(R.string.close) { _, _ ->
requireActivity().run {
setResult(FragmentActivity.RESULT_OK)
requireActivity().finish()
}
}
.create()
}
}
}

View file

@ -1,652 +0,0 @@
package info.nightscout.androidaps.plugins.general.openhumans
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.PowerManager
import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.work.*
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.BuildConfig
import info.nightscout.androidaps.R
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.database.entities.TemporaryTarget
import info.nightscout.androidaps.database.entities.TherapyEvent
import info.nightscout.androidaps.db.*
import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.interfaces.DatabaseHelperInterface
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.extensions.toConstant
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.sharedPreferences.SP
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import io.reactivex.rxkotlin.plusAssign
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.io.ByteArrayOutputStream
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class OpenHumansUploader @Inject constructor(
injector: HasAndroidInjector,
resourceHelper: ResourceHelper,
aapsLogger: AAPSLogger,
private val aapsSchedulers: AapsSchedulers,
private val sp: SP,
private val rxBus: RxBusWrapper,
private val context: Context,
private val databaseHelper: DatabaseHelperInterface,
val repository: AppRepository
) : PluginBase(
PluginDescription()
.mainType(PluginType.GENERAL)
.pluginIcon(R.drawable.open_humans_white)
.pluginName(R.string.open_humans)
.shortName(R.string.open_humans_short)
.description(R.string.donate_your_data_to_science)
.fragmentClass(OpenHumansFragment::class.qualifiedName)
.preferencesId(R.xml.pref_openhumans),
aapsLogger, resourceHelper, injector) {
companion object {
private const val OPEN_HUMANS_URL = "https://www.openhumans.org"
private const val CLIENT_ID = "oie6DvnaEOagTxSoD6BukkLPwDhVr6cMlN74Ihz1"
private const val CLIENT_SECRET = "jR0N8pkH1jOwtozHc7CsB1UPcJzFN95ldHcK4VGYIApecr8zGJox0v06xLwPLMASScngT12aIaIHXAVCJeKquEXAWG1XekZdbubSpccgNiQBmuVmIF8nc1xSKSNJltCf"
private const val REDIRECT_URL = "androidaps://setup-openhumans"
const val AUTH_URL = "https://www.openhumans.org/direct-sharing/projects/oauth2/authorize/?client_id=$CLIENT_ID&response_type=code"
const val WORK_NAME = "Open Humans"
const val NOTIFICATION_CHANNEL = "OpenHumans"
private const val COPY_NOTIFICATION_ID = 3122
private const val FAILURE_NOTIFICATION_ID = 3123
private const val SUCCESS_NOTIFICATION_ID = 3124
private const val SIGNED_OUT_NOTIFICATION_ID = 3125
const val UPLOAD_NOTIFICATION_ID = 3126
private const val UPLOAD_SEGMENT_SIZE = 10000L
}
private val openHumansAPI = OpenHumansAPI(OPEN_HUMANS_URL, CLIENT_ID, CLIENT_SECRET, REDIRECT_URL)
@Suppress("PrivatePropertyName")
private val FILE_NAME_DATE_FORMAT = SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.US).apply { timeZone = TimeZone.getTimeZone("UTC") }
private var isSetup
get() = sp.getBoolean("openhumans_is_setup", false)
set(value) = sp.putBoolean("openhumans_is_setup", value)
private var oAuthTokens: OpenHumansAPI.OAuthTokens?
get() {
return if (sp.contains("openhumans_access_token") && sp.contains("openhumans_refresh_token") && sp.contains("openhumans_expires_at")) {
OpenHumansAPI.OAuthTokens(
accessToken = sp.getStringOrNull("openhumans_access_token", null)!!,
refreshToken = sp.getStringOrNull("openhumans_refresh_token", null)!!,
expiresAt = sp.getLong("openhumans_expires_at", 0)
)
} else {
null
}
}
set(value) {
if (value != null) {
sp.putString("openhumans_access_token", value.accessToken)
sp.putString("openhumans_refresh_token", value.refreshToken)
sp.putLong("openhumans_expires_at", value.expiresAt)
} else {
sp.remove("openhumans_access_token")
sp.remove("openhumans_refresh_token")
sp.remove("openhumans_expires_at")
sp.remove("openhumans_expires_at")
}
}
var projectMemberId: String?
get() = sp.getStringOrNull("openhumans_project_member_id", null)
private set(value) {
if (value == null) sp.remove("openhumans_project_member_id")
else sp.putString("openhumans_project_member_id", value)
}
private var uploadCounter: Int
get() = sp.getInt("openhumans_counter", 1)
set(value) = sp.putInt("openhumans_counter", value)
private val appId: UUID
get() {
val id = sp.getStringOrNull("openhumans_appid", null)
return if (id == null) {
val generated = UUID.randomUUID()
sp.putString("openhumans_appid", generated.toString())
generated
} else {
UUID.fromString(id)
}
}
private var copyDisposable: Disposable? = null
private val wakeLock = (context.getSystemService(Context.POWER_SERVICE) as PowerManager)
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AndroidAPS::OpenHumans")
private val preferenceChangeDisposable = CompositeDisposable()
override fun onStart() {
super.onStart()
setupNotificationChannel()
if (isSetup) scheduleWorker(false)
preferenceChangeDisposable += rxBus.toObservable(EventPreferenceChange::class.java).subscribe {
onSharedPreferenceChanged(it)
}
}
override fun onStop() {
copyDisposable?.dispose()
cancelWorker()
preferenceChangeDisposable.clear()
super.onStop()
}
fun enqueueBGReading(glucoseValue: GlucoseValue?) = glucoseValue?.let {
insertQueueItem("BgReadings") {
put("date", glucoseValue.timestamp)
put("isValid", glucoseValue.isValid)
put("value", glucoseValue.value)
put("direction", glucoseValue.trendArrow)
put("raw", glucoseValue.raw)
put("source", glucoseValue.sourceSensor)
put("nsId", glucoseValue.interfaceIDs.nightscoutId)
}
}
// @JvmOverloads
// fun enqueueTreatment(treatment: Treatment?, deleted: Boolean = false) = treatment?.let {
// insertQueueItem("Treatments") {
// put("date", treatment.date)
// put("isValid", treatment.isValid)
// put("source", treatment.source)
// put("nsId", treatment._id)
// put("boluscalc", treatment.boluscalc)
// put("carbs", treatment.carbs)
// put("dia", treatment.dia)
// put("insulin", treatment.insulin)
// put("insulinInterfaceID", treatment.insulinInterfaceID)
// put("isSMB", treatment.isSMB)
// put("mealBolus", treatment.mealBolus)
// put("bolusCalcJson", treatment.getBoluscalc())
// put("isDeletion", deleted)
// }
// }
@JvmOverloads
fun enqueueTherapyEvent(therapyEvent: TherapyEvent, deleted: Boolean = false) = insertQueueItem("TherapyEvents") {
put("date", therapyEvent.timestamp)
put("isValid", therapyEvent.isValid)
put("nsId", therapyEvent.interfaceIDs.nightscoutId)
put("eventType", therapyEvent.type.text)
put("glucose", therapyEvent.glucose)
put("units", therapyEvent.glucoseUnit.toConstant())
put("glucoseType", therapyEvent.glucoseType?.text)
put("duration", therapyEvent.duration)
put("isDeletion", deleted)
}
// @JvmOverloads
// fun enqueueExtendedBolus(extendedBolus: ExtendedBolus, deleted: Boolean = false) = insertQueueItem("ExtendedBoluses") {
// put("date", extendedBolus.date)
// put("isValid", extendedBolus.isValid)
// put("source", extendedBolus.source)
// put("nsId", extendedBolus._id)
// put("pumpId", extendedBolus.pumpId)
// put("insulin", extendedBolus.insulin)
// put("durationInMinutes", extendedBolus.durationInMinutes)
// put("isDeletion", deleted)
// }
// @JvmOverloads
// fun enqueueProfileSwitch(profileSwitch: ProfileSwitch, deleted: Boolean = false) = insertQueueItem("ProfileSwitches") {
// put("date", profileSwitch.date)
// put("isValid", profileSwitch.isValid)
// put("source", profileSwitch.source)
// put("nsId", profileSwitch._id)
// put("isCPP", profileSwitch.isCPP)
// put("timeshift", profileSwitch.timeshift)
// put("percentage", profileSwitch.percentage)
// put("profile", JSONObject(profileSwitch.profileJson))
// put("profilePlugin", profileSwitch.profilePlugin)
// put("durationInMinutes", profileSwitch.durationInMinutes)
// put("isDeletion", deleted)
// }
// fun enqueueTotalDailyDose(tdd: TDD) = insertQueueItem("TotalDailyDoses") {
// put("double", tdd.date)
// put("double", tdd.bolus)
// put("double", tdd.basal)
// put("double", tdd.total)
// }
// @JvmOverloads
// fun enqueueTemporaryBasal(temporaryBasal: TemporaryBasal?, deleted: Boolean = false) = temporaryBasal?.let {
// insertQueueItem("TemporaryBasals") {
// put("date", temporaryBasal.date)
// put("isValid", temporaryBasal.isValid)
// put("source", temporaryBasal.source)
// put("nsId", temporaryBasal._id)
// put("pumpId", temporaryBasal.pumpId)
// put("durationInMinutes", temporaryBasal.durationInMinutes)
// put("durationInMinutes", temporaryBasal.durationInMinutes)
// put("isAbsolute", temporaryBasal.isAbsolute)
// put("percentRate", temporaryBasal.percentRate)
// put("absoluteRate", temporaryBasal.absoluteRate)
// put("isDeletion", deleted)
// }
// }
@JvmOverloads
fun enqueueTempTarget(tempTarget: TemporaryTarget?, deleted: Boolean = false) = tempTarget?.let {
insertQueueItem("TempTargets") {
put("date", tempTarget.timestamp)
put("isValid", tempTarget.isValid)
put("nsId", tempTarget.interfaceIDs_backing?.nightscoutId)
put("low", tempTarget.lowTarget)
put("high", tempTarget.highTarget)
put("reason", tempTarget.reason)
put("durationInMinutes", tempTarget.duration)
put("isDeletion", deleted)
}
}
fun enqueueSMBData(profile: JSONObject?, glucoseStatus: JSONObject?, iobData: JSONArray?, mealData: JSONObject?, currentTemp: JSONObject?, autosensData: JSONObject?, smbAllowed: Boolean, smbAlwaysAllowed: Boolean, result: JSONObject?) = insertQueueItem("APSData") {
put("algorithm", "SMB")
put("profile", profile)
put("glucoseStatus", glucoseStatus)
put("iobData", iobData)
put("mealData", mealData)
put("currentTemp", currentTemp)
put("autosensData", autosensData)
put("smbAllowed", smbAllowed)
put("smbAlwaysAllowed", smbAlwaysAllowed)
put("result", result)
}
fun enqueueAMAData(profile: JSONObject?, glucoseStatus: JSONObject?, iobData: JSONArray?, mealData: JSONObject?, currentTemp: JSONObject?, autosensData: JSONObject?, result: JSONObject?) = insertQueueItem("APSData") {
put("algorithm", "AMA")
put("profile", profile)
put("glucoseStatus", glucoseStatus)
put("iobData", iobData)
put("mealData", mealData)
put("currentTemp", currentTemp)
put("autosensData", autosensData)
put("result", result)
}
private fun insertQueueItem(file: String, structureVersion: Int = 1, generator: JSONObject.() -> Unit) {
if (oAuthTokens != null && this.isEnabled(PluginType.GENERAL)) {
try {
val jsonObject = JSONObject()
jsonObject.put("structureVersion", structureVersion)
jsonObject.put("queuedOn", System.currentTimeMillis())
generator(jsonObject)
val queueItem = OHQueueItem(
file = file,
content = jsonObject.toString()
)
databaseHelper.createOrUpdate(queueItem)
rxBus.send(OpenHumansFragment.UpdateQueueEvent)
} catch (e: JSONException) {
e.printStackTrace()
}
}
}
fun login(authCode: String): Completable =
openHumansAPI.exchangeAuthToken(authCode)
.doOnSuccess {
oAuthTokens = it
}
.flatMap { openHumansAPI.getProjectMemberId(it.accessToken) }
.doOnSuccess {
projectMemberId = it
copyExistingDataToQueue()
rxBus.send(OpenHumansFragment.UpdateViewEvent)
}
.doOnError {
aapsLogger.error("Failed to login to Open Humans", it)
}
.ignoreElement()
fun logout() {
cancelWorker()
copyDisposable?.dispose()
isSetup = false
oAuthTokens = null
projectMemberId = null
databaseHelper.clearOpenHumansQueue()
rxBus.send(OpenHumansFragment.UpdateViewEvent)
}
private fun copyExistingDataToQueue() {
copyDisposable?.dispose()
var currentProgress = 0L
var maxProgress = 0L
val increaseCounter = {
currentProgress++
//Updating the notification for every item drastically slows down the operation
if (currentProgress % 1000L == 0L) showOngoingNotification(maxProgress, currentProgress)
}
copyDisposable = Completable.fromCallable { databaseHelper.clearOpenHumansQueue() }
// .andThen(Single.defer { Single.just(databaseHelper.getCountOfAllRows() + treatmentsPlugin.service.count()) })
// .doOnSuccess { maxProgress = it }
// .flatMapObservable { Observable.defer { Observable.fromIterable(treatmentsPlugin.service.getTreatmentData()) } }
// .map { enqueueTreatment(it); increaseCounter() }
// .ignoreElements()
.andThen(Observable.defer { Observable.fromIterable(repository.compatGetBgReadingsDataFromTime(0, true).blockingGet()) })
.map { enqueueBGReading(it); increaseCounter() }
.ignoreElements()
.andThen(Observable.defer { Observable.fromIterable(repository.compatGetTherapyEventDataFromTime(0, true).blockingGet()) })
.map { enqueueTherapyEvent(it); increaseCounter() }
.ignoreElements()
// .andThen(Observable.defer { Observable.fromIterable(databaseHelper.getAllExtendedBoluses()) })
// .map { enqueueExtendedBolus(it); increaseCounter() }
// .ignoreElements()
// .andThen(Observable.defer { Observable.fromIterable(databaseHelper.getAllProfileSwitches()) })
// .map { enqueueProfileSwitch(it); increaseCounter() }
// .ignoreElements()
// .andThen(Observable.defer { Observable.fromIterable(databaseHelper.getAllTDDs()) })
// .map { enqueueTotalDailyDose(it); increaseCounter() }
// .ignoreElements()
// .andThen(Observable.defer { Observable.fromIterable(databaseHelper.getAllTemporaryBasals()) })
// .map { enqueueTemporaryBasal(it); increaseCounter() }
// .ignoreElements()
.andThen(Observable.defer { Observable.fromIterable(repository.compatGetTemporaryTargetData().blockingGet()) })
.map { enqueueTempTarget(it); increaseCounter() }
.ignoreElements()
.doOnSubscribe {
wakeLock.acquire(TimeUnit.MINUTES.toMillis(30))
showOngoingNotification()
}
.doOnComplete {
isSetup = true
scheduleWorker(false)
showSetupFinishedNotification()
}
.doOnError {
logout()
showSetupFailedNotification()
}
.doFinally {
copyDisposable = null
NotificationManagerCompat.from(context).cancel(COPY_NOTIFICATION_ID)
wakeLock.release()
}
.onErrorComplete()
.subscribeOn(aapsSchedulers.io)
.subscribe()
}
private fun showOngoingNotification(maxProgress: Long? = null, currentProgress: Long? = null) {
val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL)
.setContentTitle(resourceHelper.gs(R.string.finishing_open_humans_setup))
.setContentText(resourceHelper.gs(R.string.this_may_take_a_while))
.setStyle(NotificationCompat.BigTextStyle())
.setProgress(maxProgress?.toInt() ?: 0, currentProgress?.toInt()
?: 0, maxProgress == null || currentProgress == null)
.setOngoing(true)
.setAutoCancel(false)
.setSmallIcon(R.drawable.notif_icon)
.build()
NotificationManagerCompat.from(context).notify(COPY_NOTIFICATION_ID, notification)
}
private fun showSetupFinishedNotification() {
val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL)
.setContentTitle(resourceHelper.gs(R.string.setup_finished))
.setContentText(resourceHelper.gs(R.string.your_phone_will_upload_data))
.setStyle(NotificationCompat.BigTextStyle())
.setSmallIcon(R.drawable.notif_icon)
.build()
val notificationManager = NotificationManagerCompat.from(context)
notificationManager.notify(SUCCESS_NOTIFICATION_ID, notification)
}
private fun showSetupFailedNotification() {
val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL)
.setContentTitle(resourceHelper.gs(R.string.setup_failed))
.setContentText(resourceHelper.gs(R.string.there_was_an_error))
.setStyle(NotificationCompat.BigTextStyle())
.setSmallIcon(R.drawable.notif_icon)
.build()
val notificationManager = NotificationManagerCompat.from(context)
notificationManager.notify(FAILURE_NOTIFICATION_ID, notification)
}
@kotlin.ExperimentalStdlibApi
fun uploadDataSegmentally(): Completable =
uploadData(UPLOAD_SEGMENT_SIZE)
.repeatUntil { databaseHelper.getOHQueueSize() == 0L }
.doOnSubscribe {
aapsLogger.info(LTag.OHUPLOADER, "Starting segmental upload")
}
.doOnComplete {
aapsLogger.info(LTag.OHUPLOADER, "Segmental upload successful")
}
.doOnError {
aapsLogger.error(LTag.OHUPLOADER, "Segmental upload exceptional", it)
}
@kotlin.ExperimentalStdlibApi
@Suppress("SameParameterValue")
private fun uploadData(maxEntries: Long): Completable = gatherData(maxEntries)
.flatMap { data -> refreshAccessTokensIfNeeded().map { accessToken -> accessToken to data } }
.flatMap { uploadFile(it.first, it.second).andThen(Single.just(it.second)) }
.flatMapCompletable {
if (it.highestQueueId != null) {
removeUploadedEntriesFromQueue(it.highestQueueId)
} else {
Completable.complete()
}
}
.doOnError {
if (it is OpenHumansAPI.OHHttpException && it.code == 401 && it.detail == "Invalid token.") {
handleSignOut()
}
aapsLogger.error("Error while uploading to Open Humans", it)
}
.doOnComplete {
aapsLogger.info(LTag.OHUPLOADER, "Upload successful")
rxBus.send(OpenHumansFragment.UpdateQueueEvent)
}
.doOnSubscribe {
aapsLogger.info(LTag.OHUPLOADER, "Starting upload")
}
private fun uploadFile(accessToken: String, uploadData: UploadData) = Completable.defer {
openHumansAPI.prepareFileUpload(accessToken, uploadData.fileName, uploadData.metadata)
.flatMap { openHumansAPI.uploadFile(it.uploadURL, uploadData.content).andThen(Single.just(it.fileId)) }
.flatMapCompletable { openHumansAPI.completeFileUpload(accessToken, it) }
}
private fun refreshAccessTokensIfNeeded() = Single.defer {
val oAuthTokens = this.oAuthTokens!!
if (oAuthTokens.expiresAt <= System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1)) {
openHumansAPI.refreshAccessToken(oAuthTokens.refreshToken)
.doOnSuccess { this.oAuthTokens = it }
.map { it.accessToken }
} else {
Single.just(oAuthTokens.accessToken)
}
}
@kotlin.ExperimentalStdlibApi
private fun gatherData(maxEntries: Long) = Single.defer {
val items = databaseHelper.getAllOHQueueItems(maxEntries)
val baos = ByteArrayOutputStream()
val zos = ZipOutputStream(baos)
val tags = mutableListOf<String>()
items.groupBy { it.file }.forEach { entry ->
tags.add(entry.key)
val jsonArray = JSONArray()
entry.value.map { it.content }.forEach { jsonArray.put(JSONObject(it)) }
zos.writeFile("${entry.key}.json", jsonArray.toString().toByteArray())
}
val applicationInfo = JSONObject()
applicationInfo.put("versionName", BuildConfig.VERSION_NAME)
applicationInfo.put("versionCode", BuildConfig.VERSION_CODE)
val hasGitInfo = !BuildConfig.HEAD.endsWith("NoGitSystemAvailable", true)
val customRemote = !BuildConfig.REMOTE.equals("https://github.com/nightscout/AndroidAPS.git", true)
applicationInfo.put("hasGitInfo", hasGitInfo)
applicationInfo.put("customRemote", customRemote)
applicationInfo.put("applicationId", appId.toString())
zos.writeFile("ApplicationInfo.json", applicationInfo.toString().toByteArray())
tags.add("ApplicationInfo")
val preferences = JSONObject(sp.getAll().filterKeys { it.isAllowedKey() })
zos.writeFile("Preferences.json", preferences.toString().toByteArray())
tags.add("Preferences")
val deviceInfo = JSONObject()
deviceInfo.put("brand", Build.BRAND)
deviceInfo.put("device", Build.DEVICE)
deviceInfo.put("manufacturer", Build.MANUFACTURER)
deviceInfo.put("model", Build.MODEL)
deviceInfo.put("product", Build.PRODUCT)
zos.writeFile("DeviceInfo.json", deviceInfo.toString().toByteArray())
tags.add("DeviceInfo")
val displayMetrics = DisplayMetrics()
(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.getMetrics(displayMetrics)
val displayInfo = JSONObject()
displayInfo.put("height", displayMetrics.heightPixels)
displayInfo.put("width", displayMetrics.widthPixels)
displayInfo.put("density", displayMetrics.density)
displayInfo.put("scaledDensity", displayMetrics.scaledDensity)
displayInfo.put("xdpi", displayMetrics.xdpi)
displayInfo.put("ydpi", displayMetrics.ydpi)
zos.writeFile("DisplayInfo.json", displayInfo.toString().toByteArray())
tags.add("DisplayInfo")
val uploadNumber = this.uploadCounter++
val uploadDate = Date()
val uploadInfo = JSONObject()
uploadInfo.put("fileVersion", 1)
uploadInfo.put("counter", uploadNumber)
uploadInfo.put("timestamp", uploadDate.time)
uploadInfo.put("utcOffset", TimeZone.getDefault().getOffset(uploadDate.time))
zos.writeFile("UploadInfo.json", uploadInfo.toString().toByteArray())
tags.add("UploadInfo")
zos.close()
val bytes = baos.toByteArray()
Single.just(UploadData(
fileName = "upload-num$uploadNumber-ver1-date${FILE_NAME_DATE_FORMAT.format(uploadDate)}-appid${appId.toString().replace("-", "")}.zip",
metadata = OpenHumansAPI.FileMetadata(
tags = tags,
description = "AndroidAPS Database Upload",
md5 = MessageDigest.getInstance("MD5").digest(bytes).toHexString(),
creationDate = uploadDate.time
),
content = bytes,
highestQueueId = items.map { it.id }.maxOrNull()
))
}
private fun ZipOutputStream.writeFile(name: String, bytes: ByteArray) {
putNextEntry(ZipEntry(name))
write(bytes)
closeEntry()
}
private fun removeUploadedEntriesFromQueue(highestId: Long) = Completable.fromCallable {
databaseHelper.removeAllOHQueueItemsWithIdSmallerThan(highestId)
}
private fun handleSignOut() {
val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL)
.setContentTitle(resourceHelper.gs(R.string.you_have_been_signed_out_of_open_humans))
.setContentText(resourceHelper.gs(R.string.click_here_to_sign_in_again_if_this_wasnt_on_purpose))
.setStyle(NotificationCompat.BigTextStyle())
.setSmallIcon(R.drawable.notif_icon)
.setAutoCancel(true)
.setContentIntent(PendingIntent.getActivity(
context,
0,
Intent(context, OpenHumansLoginActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
},
0
))
.build()
NotificationManagerCompat.from(context).notify(SIGNED_OUT_NOTIFICATION_ID, notification)
logout()
}
private fun cancelWorker() {
WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME)
}
private fun scheduleWorker(replace: Boolean) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresCharging(sp.getBoolean("key_oh_charging_only", false))
.build()
val workRequest = PeriodicWorkRequestBuilder<OHUploadWorker>(1, TimeUnit.DAYS)
.setConstraints(constraints)
.setBackoffCriteria(BackoffPolicy.LINEAR, 20, TimeUnit.MINUTES)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(WORK_NAME, if (replace) ExistingPeriodicWorkPolicy.REPLACE else ExistingPeriodicWorkPolicy.KEEP, workRequest)
}
private fun setupNotificationChannel() {
val notificationManagerCompat = NotificationManagerCompat.from(context)
notificationManagerCompat.createNotificationChannel(NotificationChannel(
NOTIFICATION_CHANNEL,
resourceHelper.gs(R.string.open_humans),
NotificationManager.IMPORTANCE_DEFAULT
))
}
private class UploadData(
val fileName: String,
val metadata: OpenHumansAPI.FileMetadata,
val content: ByteArray,
val highestQueueId: Long?
)
@Suppress("PrivatePropertyName")
private val HEX_DIGITS = "0123456789ABCDEF".toCharArray()
private fun ByteArray.toHexString(): String {
val stringBuilder = StringBuilder()
map { it.toInt() }.forEach {
stringBuilder.append(HEX_DIGITS[(it shr 4) and 0x0F])
stringBuilder.append(HEX_DIGITS[it and 0x0F])
}
return stringBuilder.toString()
}
private fun onSharedPreferenceChanged(event: EventPreferenceChange) {
if (event.changedKey == "key_oh_charging_only" && isSetup) scheduleWorker(true)
}
}

View file

@ -1,46 +1,56 @@
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.entities.ExtendedBolus
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.database.entities.TemporaryBasal
import info.nightscout.androidaps.database.entities.TemporaryTarget
import info.nightscout.androidaps.extensions.convertedToPercent
import info.nightscout.androidaps.extensions.toStringFull
import info.nightscout.androidaps.extensions.toStringShort
import info.nightscout.androidaps.extensions.valueToUnits
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.FixedLineGraphSeries
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.Scale
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.ScaledDataPoint
import info.nightscout.androidaps.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.DateUtil
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.collections.ArrayList
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
@Singleton
class OverviewData @Inject constructor(
private val injector: HasAndroidInjector,
private val aapsLogger: AAPSLogger,
private val resourceHelper: ResourceHelper,
private val dateUtil: DateUtil,
private val sp: SP,
private val activePlugin: ActivePlugin,
private val defaultValueHelper: DefaultValueHelper,
private val profileFunction: ProfileFunction
private val profileFunction: ProfileFunction,
private val config: Config,
private val loopPlugin: LoopPlugin,
private val nsDeviceStatus: NSDeviceStatus,
private val repository: AppRepository,
private val overviewMenus: OverviewMenus,
private val iobCobCalculator: IobCobCalculator,
private val translator: Translator
) {
enum class Property {
@ -53,7 +63,8 @@ class OverviewData @Inject constructor(
BG,
IOB_COB,
SENSITIVITY,
GRAPH
GRAPH,
PUMPSTATUS
}
var rangeToDisplay = 6 // for graph
@ -61,6 +72,42 @@ class OverviewData @Inject constructor(
var fromTime: Long = 0
var endTime: Long = 0
fun reset() {
pumpStatus = ""
calcProgress = ""
lastBg = null
temporaryBasal = null
extendedBolus = null
bolusIob = null
basalIob = null
cobInfo = null
lastCarbsTime = 0L
temporaryTarget = null
lastAutosensData = null
bgReadingsArray = ArrayList()
bucketedGraphSeries = PointsWithLabelGraphSeries()
bgReadingGraphSeries = PointsWithLabelGraphSeries()
predictionsGraphSeries = PointsWithLabelGraphSeries()
baseBasalGraphSeries = LineGraphSeries()
tempBasalGraphSeries = LineGraphSeries()
basalLineGraphSeries = LineGraphSeries()
absoluteBasalGraphSeries = LineGraphSeries()
activitySeries = FixedLineGraphSeries()
activityPredictionSeries = FixedLineGraphSeries()
iobSeries = FixedLineGraphSeries()
absIobSeries = FixedLineGraphSeries()
iobPredictions1Series = PointsWithLabelGraphSeries()
iobPredictions2Series = PointsWithLabelGraphSeries()
minusBgiSeries = FixedLineGraphSeries()
minusBgiHistSeries = FixedLineGraphSeries()
cobSeries = FixedLineGraphSeries()
cobMinFailOverSeries = PointsWithLabelGraphSeries()
deviationsSeries = BarGraphSeries()
ratioSeries = LineGraphSeries()
dsMaxSeries = LineGraphSeries()
dsMinSeries = LineGraphSeries()
}
fun initRange() {
rangeToDisplay = sp.getInt(R.string.key_rangetodisplay, 6)
@ -78,25 +125,10 @@ class OverviewData @Inject constructor(
}
/*
* PROFILE
* PUMP STATUS
*/
var profile: Profile? = null
var profileName: String? = null
var profileNameWithRemainingTime: String? = null
val profileBackgroudColor: 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)
var pumpStatus: String = ""
/*
* CALC PROGRESS
@ -133,13 +165,14 @@ class OverviewData @Inject constructor(
val temporaryBasalText: String
get() =
profile?.let { profile ->
profileFunction.getProfile()?.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 ->
get() = profileFunction.getProfile()?.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)
@ -149,7 +182,7 @@ class OverviewData @Inject constructor(
val temporaryBasalIcon: Int
get() =
profile?.let { profile ->
profileFunction.getProfile()?.let { profile ->
temporaryBasal?.let { temporaryBasal ->
val percentRate = temporaryBasal.convertedToPercent(dateUtil.now(), profile)
when {
@ -173,7 +206,10 @@ class OverviewData @Inject constructor(
val extendedBolusText: String
get() =
extendedBolus?.let { extendedBolus ->
if (activePlugin.activePump.isFakingTempsByExtendedBoluses) resourceHelper.gs(R.string.pump_basebasalrate, extendedBolus.rate)
if (!extendedBolus.isInProgress(dateUtil)) {
this@OverviewData.extendedBolus = null
""
} else if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) resourceHelper.gs(R.string.pump_basebasalrate, extendedBolus.rate)
else ""
} ?: ""
@ -211,7 +247,7 @@ class OverviewData @Inject constructor(
* TEMP TARGET
*/
var temporarytarget: TemporaryTarget? = null
var temporaryTarget: TemporaryTarget? = null
/*
* SENSITIVITY
@ -278,4 +314,520 @@ class OverviewData @Inject constructor(
var dsMaxSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
var dsMinSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
@Synchronized
@Suppress("SameParameterValue", "UNUSED_PARAMETER")
fun prepareBgData(from: String) {
// val start = dateUtil.now()
maxBgValue = Double.MIN_VALUE
bgReadingsArray = repository.compatGetBgReadingsDataFromTime(fromTime, toTime, false).blockingGet()
val bgListArray: MutableList<DataPointWithLabelInterface> = java.util.ArrayList()
for (bg in bgReadingsArray) {
if (bg.timestamp < fromTime || bg.timestamp > toTime) continue
if (bg.value > maxBgValue) maxBgValue = bg.value
bgListArray.add(GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper))
}
bgReadingGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })
maxBgValue = Profile.fromMgdlToUnits(maxBgValue, profileFunction.getUnits())
if (defaultValueHelper.determineHighLine() > maxBgValue) maxBgValue = defaultValueHelper.determineHighLine()
maxBgValue = addUpperChartMargin(maxBgValue)
// profiler.log(LTag.UI, "prepareBgData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
fun preparePredictions(from: String) {
// val start = dateUtil.now()
val apsResult = if (config.APS) loopPlugin.lastRun?.constraintsProcessed else nsDeviceStatus.getAPSResult(injector)
val predictionsAvailable = if (config.APS) loopPlugin.lastRun?.request?.hasPredictions == true else config.NSCLIENT
val menuChartSettings = overviewMenus.setting
// align to hours
val calendar = Calendar.getInstance().also {
it.timeInMillis = System.currentTimeMillis()
it[Calendar.MILLISECOND] = 0
it[Calendar.SECOND] = 0
it[Calendar.MINUTE] = 0
it.add(Calendar.HOUR, 1)
}
if (predictionsAvailable && apsResult != null && menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) {
var predictionHours = (ceil(apsResult.latestPredictionsTime - System.currentTimeMillis().toDouble()) / (60 * 60 * 1000)).toInt()
predictionHours = min(2, predictionHours)
predictionHours = max(0, predictionHours)
val hoursToFetch = rangeToDisplay - predictionHours
toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific
fromTime = toTime - T.hours(hoursToFetch.toLong()).msecs()
endTime = toTime + T.hours(predictionHours.toLong()).msecs()
} else {
toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific
fromTime = toTime - T.hours(rangeToDisplay.toLong()).msecs()
endTime = toTime
}
val bgListArray: MutableList<DataPointWithLabelInterface> = java.util.ArrayList()
val predictions: MutableList<GlucoseValueDataPoint>? = apsResult?.predictions
?.map { bg -> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper) }
?.toMutableList()
if (predictions != null) {
predictions.sortWith { o1: GlucoseValueDataPoint, o2: GlucoseValueDataPoint -> o1.x.compareTo(o2.x) }
for (prediction in predictions) if (prediction.data.value >= 40) bgListArray.add(prediction)
}
predictionsGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })
// profiler.log(LTag.UI, "preparePredictions() $from", start)
}
@Synchronized
@Suppress("SameParameterValue", "UNUSED_PARAMETER")
fun prepareBucketedData(from: String) {
// val start = dateUtil.now()
val bucketedData = iobCobCalculator.ads.getBucketedDataTableCopy() ?: return
if (bucketedData.isEmpty()) {
aapsLogger.debug("No bucketed data.")
return
}
val bucketedListArray: MutableList<DataPointWithLabelInterface> = java.util.ArrayList()
for (inMemoryGlucoseValue in bucketedData) {
if (inMemoryGlucoseValue.timestamp < fromTime || inMemoryGlucoseValue.timestamp > toTime) continue
bucketedListArray.add(InMemoryGlucoseValueDataPoint(inMemoryGlucoseValue, profileFunction, resourceHelper))
}
bucketedGraphSeries = PointsWithLabelGraphSeries(Array(bucketedListArray.size) { i -> bucketedListArray[i] })
// profiler.log(LTag.UI, "prepareBucketedData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
fun prepareBasalData(from: String) {
// val start = dateUtil.now()
maxBasalValueFound = 0.0
val baseBasalArray: MutableList<ScaledDataPoint> = java.util.ArrayList()
val tempBasalArray: MutableList<ScaledDataPoint> = java.util.ArrayList()
val basalLineArray: MutableList<ScaledDataPoint> = java.util.ArrayList()
val absoluteBasalLineArray: MutableList<ScaledDataPoint> = java.util.ArrayList()
var lastLineBasal = 0.0
var lastAbsoluteLineBasal = -1.0
var lastBaseBasal = 0.0
var lastTempBasal = 0.0
var time = fromTime
while (time < toTime) {
val profile = profileFunction.getProfile(time)
if (profile == null) {
time += 60 * 1000L
continue
}
val basalData = iobCobCalculator.getBasalData(profile, time)
val baseBasalValue = basalData.basal
var absoluteLineValue = baseBasalValue
var tempBasalValue = 0.0
var basal = 0.0
if (basalData.isTempBasalRunning) {
tempBasalValue = basalData.tempBasalAbsolute
absoluteLineValue = tempBasalValue
if (tempBasalValue != lastTempBasal) {
tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale))
tempBasalArray.add(ScaledDataPoint(time, tempBasalValue.also { basal = it }, basalScale))
}
if (lastBaseBasal != 0.0) {
baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale))
baseBasalArray.add(ScaledDataPoint(time, 0.0, basalScale))
lastBaseBasal = 0.0
}
} else {
if (baseBasalValue != lastBaseBasal) {
baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale))
baseBasalArray.add(ScaledDataPoint(time, baseBasalValue.also { basal = it }, basalScale))
lastBaseBasal = baseBasalValue
}
if (lastTempBasal != 0.0) {
tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale))
tempBasalArray.add(ScaledDataPoint(time, 0.0, basalScale))
}
}
if (baseBasalValue != lastLineBasal) {
basalLineArray.add(ScaledDataPoint(time, lastLineBasal, basalScale))
basalLineArray.add(ScaledDataPoint(time, baseBasalValue, basalScale))
}
if (absoluteLineValue != lastAbsoluteLineBasal) {
absoluteBasalLineArray.add(ScaledDataPoint(time, lastAbsoluteLineBasal, basalScale))
absoluteBasalLineArray.add(ScaledDataPoint(time, basal, basalScale))
}
lastAbsoluteLineBasal = absoluteLineValue
lastLineBasal = baseBasalValue
lastTempBasal = tempBasalValue
maxBasalValueFound = max(maxBasalValueFound, max(tempBasalValue, baseBasalValue))
time += 60 * 1000L
}
// final points
basalLineArray.add(ScaledDataPoint(toTime, lastLineBasal, basalScale))
baseBasalArray.add(ScaledDataPoint(toTime, lastBaseBasal, basalScale))
tempBasalArray.add(ScaledDataPoint(toTime, lastTempBasal, basalScale))
absoluteBasalLineArray.add(ScaledDataPoint(toTime, lastAbsoluteLineBasal, basalScale))
// create series
baseBasalGraphSeries = LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = resourceHelper.gc(R.color.basebasal)
it.thickness = 0
}
tempBasalGraphSeries = LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also {
it.isDrawBackground = true
it.backgroundColor = resourceHelper.gc(R.color.tempbasal)
it.thickness = 0
}
basalLineGraphSeries = LineGraphSeries(Array(basalLineArray.size) { i -> basalLineArray[i] }).also {
it.setCustomPaint(Paint().also { paint ->
paint.style = Paint.Style.STROKE
paint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2
paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f)
paint.color = resourceHelper.gc(R.color.basal)
})
}
absoluteBasalGraphSeries = LineGraphSeries(Array(absoluteBasalLineArray.size) { i -> absoluteBasalLineArray[i] }).also {
it.setCustomPaint(Paint().also { absolutePaint ->
absolutePaint.style = Paint.Style.STROKE
absolutePaint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2
absolutePaint.color = resourceHelper.gc(R.color.basal)
})
}
// profiler.log(LTag.UI, "prepareBasalData() $from", start)
}
@Suppress("UNUSED_PARAMETER")
@Synchronized
fun prepareTemporaryTargetData(from: String) {
// val start = dateUtil.now()
val profile = profileFunction.getProfile() ?: 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.getBolusesDataFromTimeToTime(fromTime, endTime, true).blockingGet()
.map { BolusDataPoint(it, resourceHelper, activePlugin, defaultValueHelper) }
.filter { it.data.type == Bolus.Type.NORMAL || it.data.type == Bolus.Type.SMB }
.forEach {
it.y = getNearestBg(it.x.toLong())
filteredTreatments.add(it)
}
repository.getCarbsDataFromTimeToTimeExpanded(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

@ -26,6 +26,7 @@ import dagger.android.HasAndroidInjector
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.ProfileSealed
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources
@ -38,6 +39,7 @@ 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.*
@ -57,10 +59,13 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProv
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
import info.nightscout.androidaps.plugins.source.DexcomPlugin
import info.nightscout.androidaps.plugins.source.XdripPlugin
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import info.nightscout.androidaps.queue.CommandQueue
import info.nightscout.androidaps.skins.SkinProvider
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.TrendCalculator
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.protection.ProtectionCheck
@ -72,6 +77,7 @@ import info.nightscout.androidaps.utils.wizard.QuickWizard
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.collections.ArrayList
import kotlin.math.abs
@ -92,7 +98,6 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
@Inject lateinit var nsDeviceStatus: NSDeviceStatus
@Inject lateinit var loopPlugin: LoopPlugin
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var treatmentsPlugin: TreatmentsPlugin
@Inject lateinit var iobCobCalculator: IobCobCalculator
@Inject lateinit var dexcomPlugin: DexcomPlugin
@Inject lateinit var dexcomMediator: DexcomPlugin.DexcomMediator
@ -108,7 +113,6 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
@Inject lateinit var trendCalculator: TrendCalculator
@Inject lateinit var config: Config
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var databaseHelper: DatabaseHelperInterface
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var repository: AppRepository
@Inject lateinit var glucoseStatusProvider: GlucoseStatusProvider
@ -121,7 +125,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
private var smallHeight = false
private lateinit var dm: DisplayMetrics
private var axisWidth: Int = 0
private var refreshLoop: Runnable? = null
private lateinit var refreshLoop: Runnable
private lateinit var handler: Handler
private val secondaryGraphs = ArrayList<GraphView>()
@ -140,7 +144,10 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
_binding = it
//check screen width
dm = DisplayMetrics()
activity?.windowManager?.defaultDisplay?.getMetrics(dm)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R)
activity?.display?.getRealMetrics(dm)
else
@Suppress("DEPRECATION") activity?.windowManager?.defaultDisplay?.getMetrics(dm)
}.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -157,8 +164,6 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
skinProvider.activeSkin().preProcessLandscapeOverviewLayout(dm, view, landscape, resourceHelper.gb(R.bool.isTablet), smallHeight)
binding.nsclientLayout.visibility = config.NSCLIENT.toVisibility()
binding.loopPumpStatusLayout.pumpStatus.setBackgroundColor(resourceHelper.gc(R.color.colorInitializingBorder))
binding.notifications.setHasFixedSize(false)
binding.notifications.layoutManager = LinearLayoutManager(view.context)
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
@ -177,6 +182,8 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
overviewData.rangeToDisplay = if (overviewData.rangeToDisplay > 24) 6 else overviewData.rangeToDisplay
sp.putInt(R.string.key_rangetodisplay, overviewData.rangeToDisplay)
overviewData.initRange()
overviewData.prepareBucketedData("EventBucketedDataCreated")
overviewData.prepareBgData("EventBucketedDataCreated")
updateGUI("rangeChange", OverviewData.Property.GRAPH)
rxBus.send(EventPreferenceChange(resourceHelper, R.string.key_rangetodisplay))
sp.putBoolean(R.string.key_objectiveusescale, true)
@ -185,10 +192,10 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
prepareGraphsIfNeeded(overviewMenus.setting.size)
overviewMenus.setupChartMenu(binding.graphsLayout.chartMenuButton)
binding.loopPumpStatusLayout.activeProfile.setOnClickListener(this)
binding.loopPumpStatusLayout.activeProfile.setOnLongClickListener(this)
binding.loopPumpStatusLayout.tempTarget.setOnClickListener(this)
binding.loopPumpStatusLayout.tempTarget.setOnLongClickListener(this)
binding.activeProfile.setOnClickListener(this)
binding.activeProfile.setOnLongClickListener(this)
binding.tempTarget.setOnClickListener(this)
binding.tempTarget.setOnLongClickListener(this)
binding.buttonsLayout.acceptTempButton.setOnClickListener(this)
binding.buttonsLayout.treatmentButton.setOnClickListener(this)
binding.buttonsLayout.wizardButton.setOnClickListener(this)
@ -200,7 +207,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
binding.buttonsLayout.quickWizardButton.setOnLongClickListener(this)
binding.infoLayout.apsMode.setOnClickListener(this)
binding.infoLayout.apsMode.setOnLongClickListener(this)
binding.loopPumpStatusLayout.activeProfile.setOnLongClickListener(this)
binding.activeProfile.setOnLongClickListener(this)
}
@Synchronized
@ -241,10 +248,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
.toObservable(EventNewOpenLoopNotification::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ scheduleUpdateGUI("EventNewOpenLoopNotification") }, fabricPrivacy::logException))
disposable.add(rxBus
disposable += rxBus
.toObservable(EventPumpStatusChanged::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ updatePumpStatus(it) }, fabricPrivacy::logException))
.delay (30, TimeUnit.MILLISECONDS, aapsSchedulers.main)
.subscribe({
overviewData.pumpStatus = it.getStatus(resourceHelper)
updateGUI("EventPumpStatusChanged", OverviewData.Property.PUMPSTATUS)
}, fabricPrivacy::logException)
refreshLoop = Runnable {
overviewPlugin.refreshLoop("refreshLoop")
@ -374,7 +385,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
}
R.id.temp_target -> v.performClick()
R.id.active_profile -> activity?.let { activity -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { ProfileSwitchDialog().show(childFragmentManager, "Overview") }) }
R.id.active_profile -> activity?.let { activity -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { ProfileSwitchDialog().show(childFragmentManager, "ProfileSwitchDialog") }) }
}
return false
@ -402,18 +413,6 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
}
}
private fun updatePumpStatus(event: EventPumpStatusChanged) {
val status = event.getStatus(resourceHelper)
if (status != "") {
binding.loopPumpStatusLayout.pumpStatus.text = status
binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.VISIBLE
binding.loopPumpStatusLayout.loopLayout.visibility = View.GONE
} else {
binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.GONE
binding.loopPumpStatusLayout.loopLayout.visibility = View.VISIBLE
}
}
@SuppressLint("SetTextI18n")
private fun processButtonsVisibility() {
val lastBG = iobCobCalculator.ads.lastBg()
@ -585,9 +584,9 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
task = null
}
}
handler.removeCallbacks(task)
task?.let { handler.removeCallbacks(it) }
task = UpdateRunnable()
handler.postDelayed(task, 500)
task?.let { handler.postDelayed(it, 500) }
}
@Suppress("UNUSED_PARAMETER")
@ -595,15 +594,12 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
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
if (profileFunction.getProfile() == null) {
binding.activeProfile.setText(R.string.noprofileset)
binding.activeProfile.setBackgroundColor(resourceHelper.gc(R.color.errorAlertBackground))
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
@ -623,7 +619,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
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 = "Δ " + resourceHelper.gs(R.string.notavailable)
binding.infoLayout.deltaLarge.text = ""
binding.infoLayout.delta.text = "Δ " + resourceHelper.gs(R.string.notavailable)
binding.infoLayout.avgDelta.text = ""
binding.infoLayout.longAvgDelta.text = ""
@ -638,10 +634,25 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
}
OverviewData.Property.PROFILE -> {
binding.loopPumpStatusLayout.activeProfile.text = overviewData.profileNameWithRemainingTime
?: ""
binding.loopPumpStatusLayout.activeProfile.setBackgroundColor(overviewData.profileBackgroudColor)
binding.loopPumpStatusLayout.activeProfile.setTextColor(overviewData.profileTextColor)
val profileBackgroundColor =
profileFunction.getProfile()?.let {
val profile = (it as ProfileSealed.EPS).value
if (profile.originalPercentage != 100 || profile.originalTimeshift != 0L || profile.originalDuration != 0L)
resourceHelper.gc(R.color.ribbonWarning)
else resourceHelper.gc(R.color.ribbonDefault)
} ?: resourceHelper.gc(R.color.ribbonTextDefault)
val profileTextColor =
profileFunction.getProfile()?.let {
val profile = (it as ProfileSealed.EPS).value
if (profile.originalPercentage != 100 || profile.originalTimeshift != 0L || profile.originalDuration != 0L)
resourceHelper.gc(R.color.ribbonTextWarning)
else resourceHelper.gc(R.color.ribbonTextDefault)
} ?: resourceHelper.gc(R.color.ribbonTextDefault)
binding.activeProfile.text = profileFunction.getProfileNameWithRemainingTime()
binding.activeProfile.setBackgroundColor(profileBackgroundColor)
binding.activeProfile.setTextColor(profileTextColor)
}
OverviewData.Property.TEMPORARY_BASAL -> {
@ -655,7 +666,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
OverviewData.Property.EXTENDED_BOLUS -> {
binding.infoLayout.extendedBolus.text = overviewData.extendedBolusText
binding.infoLayout.extendedBolus.setOnClickListener {
binding.infoLayout.extendedLayout.setOnClickListener {
activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.extended_bolus), overviewData.extendedBolusDialogText) }
}
binding.infoLayout.extendedLayout.visibility = (overviewData.extendedBolus != null && !pump.isFakingTempsByExtendedBoluses).toVisibility()
@ -677,14 +688,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
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
var cobText = overviewData.cobInfo?.displayText(resourceHelper, dateUtil, buildHelper.isDev()) ?: resourceHelper.gs(R.string.value_unavailable_short)
val constraintsProcessed = loopPlugin.lastRun?.constraintsProcessed
val lastRun = loopPlugin.lastRun
@ -692,7 +696,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
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)
cobText += " | " + constraintsProcessed.carbsReq + " " + resourceHelper.gs(R.string.required)
}
if (carbAnimation?.isRunning == false)
carbAnimation?.start()
@ -701,36 +705,38 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
carbAnimation?.selectDrawable(0)
}
}
binding.infoLayout.cob.text = cobText
}
OverviewData.Property.TEMPORARY_TARGET -> {
// temp target
val tempTarget = overviewData.temporarytarget
if (overviewData.temporaryTarget?.isInProgress(dateUtil) == false) overviewData.temporaryTarget = null
val tempTarget = overviewData.temporaryTarget
if (tempTarget != null) {
binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning))
binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning))
binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(tempTarget.lowTarget, tempTarget.highTarget, GlucoseUnit.MGDL, units) + " " + dateUtil.untilString(tempTarget.end, resourceHelper)
binding.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning))
binding.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning))
binding.tempTarget.text = Profile.toTargetRangeString(tempTarget.lowTarget, tempTarget.highTarget, GlucoseUnit.MGDL, units) + " " + dateUtil.untilString(tempTarget.end, resourceHelper)
} else {
// If the target is not the same as set in the profile then oref has overridden it
overviewData.profile?.let { profile ->
profileFunction.getProfile()?.let { profile ->
val targetUsed = loopPlugin.lastRun?.constraintsProcessed?.targetBG ?: 0.0
if (targetUsed != 0.0 && abs(profile.getTargetMgdl() - targetUsed) > 0.01) {
aapsLogger.debug("Adjusted target. Profile: ${profile.getTargetMgdl()} APS: $targetUsed")
binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(targetUsed, targetUsed, GlucoseUnit.MGDL, units)
binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning))
binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.tempTargetBackground))
binding.tempTarget.text = Profile.toTargetRangeString(targetUsed, targetUsed, GlucoseUnit.MGDL, units)
binding.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning))
binding.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.tempTargetBackground))
} else {
binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextDefault))
binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonDefault))
binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(profile.getTargetLowMgdl(), profile.getTargetHighMgdl(), GlucoseUnit.MGDL, units)
binding.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextDefault))
binding.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonDefault))
binding.tempTarget.text = Profile.toTargetRangeString(profile.getTargetLowMgdl(), profile.getTargetHighMgdl(), GlucoseUnit.MGDL, units)
}
}
}
}
OverviewData.Property.GRAPH -> {
val graphData = GraphData(injector, binding.graphsLayout.bgGraph)
val graphData = GraphData(injector, binding.graphsLayout.bgGraph, overviewData)
val menuChartSettings = overviewMenus.setting
graphData.addInRangeArea(overviewData.fromTime, overviewData.endTime, defaultValueHelper.determineLowLine(), defaultValueHelper.determineHighLine())
graphData.addBgReadings(menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal])
@ -738,7 +744,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
graphData.addTreatments()
if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal])
graphData.addActivity(0.8)
if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal])
if ((pump.pumpDescription.isTempBasalCapable || config.NSCLIENT) && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal])
graphData.addBasals()
graphData.addTargetLine()
graphData.addNowLine(dateUtil.now())
@ -755,7 +761,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
val now = System.currentTimeMillis()
for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) {
val secondGraphData = GraphData(injector, secondaryGraphs[g])
val secondGraphData = GraphData(injector, secondaryGraphs[g], overviewData)
var useABSForScale = false
var useIobForScale = false
var useCobForScale = false
@ -780,7 +786,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
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)
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
secondGraphData.formatAxis(overviewData.fromTime, overviewData.endTime)
@ -818,6 +824,12 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
String.format(Locale.ENGLISH, "%.0f%%", autosensData.autosensResult.ratio * 100)
} ?: ""
}
OverviewData.Property.PUMPSTATUS -> {
val status = overviewData.pumpStatus
binding.pumpStatus.text = status
binding.pumpStatusLayout.visibility = (status != "").toVisibility()
}
}
}
}

View file

@ -113,6 +113,9 @@ class OverviewMenus @Inject constructor(
if (m == CharType.PRE) insert = predictionsAvailable
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) {
val item = popup.menu.add(Menu.NONE, m.ordinal + 100 * (g + 1), Menu.NONE, resourceHelper.gs(m.nameId))
val title = item.title

View file

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

View file

@ -74,7 +74,7 @@ class StatusLightHandler @Inject constructor(
private fun handleAge(view: TextView?, type: TherapyEvent.Type, @StringRes warnSettings: Int, defaultWarnThreshold: Double, @StringRes urgentSettings: Int, defaultUrgentThreshold: Double) {
val warn = sp.getDouble(warnSettings, defaultWarnThreshold)
val urgent = sp.getDouble(urgentSettings, defaultUrgentThreshold)
val therapyEvent = repository.getLastTherapyRecord(type).blockingGet()
val therapyEvent = repository.getLastTherapyRecordUpToNow(type).blockingGet()
if (therapyEvent is ValueWrapper.Existing) {
warnColors.setColorByAge(view, therapyEvent.value, warn, urgent)
view?.text = therapyEvent.value.age(resourceHelper.shortTextMode(), resourceHelper, dateUtil)

View file

@ -64,12 +64,12 @@ class QuickWizardListActivity : NoSplashAppCompatActivity() {
val manager = fragmentManager
val editQuickWizardDialog = EditQuickWizardDialog()
val bundle = Bundle()
bundle.putInt("position", adapterPosition)
bundle.putInt("position", bindingAdapterPosition)
editQuickWizardDialog.arguments = bundle
editQuickWizardDialog.show(manager, "EditQuickWizardDialog")
}
removeButton.setOnClickListener {
quickWizard.remove(adapterPosition)
quickWizard.remove(bindingAdapterPosition)
rxBus.send(EventQuickWizardChange())
}
}

View file

@ -26,16 +26,16 @@ import kotlin.math.max
class GraphData(
injector: HasAndroidInjector,
private val graph: GraphView
private val graph: GraphView,
private val overviewData: OverviewData
) {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var defaultValueHelper: DefaultValueHelper
@Inject lateinit var overviewData: OverviewData
var maxY = Double.MIN_VALUE
private var maxY = Double.MIN_VALUE
private var minY = Double.MAX_VALUE
private val units: GlucoseUnit
private val series: MutableList<Series<*>> = ArrayList()
@ -76,7 +76,7 @@ class GraphData(
addSeries(overviewData.tempBasalGraphSeries)
addSeries(overviewData.basalLineGraphSeries)
addSeries(overviewData.absoluteBasalGraphSeries)
overviewData.basalScale.setMultiplier(maxY * scale / overviewData.maxBasalValueFound)
overviewData.basalScale.multiplier = maxY * scale / overviewData.maxBasalValueFound
}
fun addTargetLine() {
@ -91,7 +91,7 @@ class GraphData(
fun addActivity(scale: Double) {
addSeries(overviewData.activitySeries)
addSeries(overviewData.activityPredictionSeries)
overviewData.actScale.setMultiplier(maxY * scale / overviewData.maxIAValue)
overviewData.actScale.multiplier = maxY * scale / overviewData.maxIAValue
}
//Function below show -BGI to be able to compare curves with deviations
@ -100,7 +100,7 @@ class GraphData(
maxY = overviewData.maxBGIValue
minY = -overviewData.maxBGIValue
}
overviewData.bgiScale.setMultiplier(maxY * scale / overviewData.maxBGIValue)
overviewData.bgiScale.multiplier = maxY * scale / overviewData.maxBGIValue
addSeries(overviewData.minusBgiSeries)
addSeries(overviewData.minusBgiHistSeries)
}
@ -111,7 +111,7 @@ class GraphData(
maxY = overviewData.maxIobValueFound
minY = -overviewData.maxIobValueFound
}
overviewData.iobScale.setMultiplier(maxY * scale / overviewData.maxIobValueFound)
overviewData.iobScale.multiplier = maxY * scale / overviewData.maxIobValueFound
addSeries(overviewData.iobSeries)
addSeries(overviewData.iobPredictions1Series)
addSeries(overviewData.iobPredictions2Series)
@ -123,7 +123,7 @@ class GraphData(
maxY = overviewData.maxIobValueFound
minY = -overviewData.maxIobValueFound
}
overviewData.iobScale.setMultiplier(maxY * scale / overviewData.maxIobValueFound)
overviewData.iobScale.multiplier = maxY * scale / overviewData.maxIobValueFound
addSeries(overviewData.absIobSeries)
}
@ -133,7 +133,7 @@ class GraphData(
maxY = overviewData.maxCobValueFound
minY = 0.0
}
overviewData.cobScale.setMultiplier(maxY * scale / overviewData.maxCobValueFound)
overviewData.cobScale.multiplier = maxY * scale / overviewData.maxCobValueFound
addSeries(overviewData.cobSeries)
addSeries(overviewData.cobMinFailOverSeries)
}
@ -144,7 +144,7 @@ class GraphData(
maxY = overviewData.maxDevValueFound
minY = -maxY
}
overviewData.devScale.setMultiplier(maxY * scale / overviewData.maxDevValueFound)
overviewData.devScale.multiplier = maxY * scale / overviewData.maxDevValueFound
addSeries(overviewData.deviationsSeries)
}
@ -153,20 +153,32 @@ class GraphData(
if (useForScale) {
maxY = 100.0 + max(overviewData.maxRatioValueFound, abs(overviewData.minRatioValueFound))
minY = 100.0 - max(overviewData.maxRatioValueFound, abs(overviewData.minRatioValueFound))
overviewData.ratioScale.setMultiplier(1.0)
} else
overviewData.ratioScale.setMultiplier(maxY * scale / max(overviewData.maxRatioValueFound, abs(overviewData.minRatioValueFound)))
overviewData.ratioScale.multiplier = 1.0
overviewData.ratioScale.shift = 100.0
} 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)
fun addDeviationSlope(useForScale: Boolean, scale: Double) {
fun addDeviationSlope(useForScale: Boolean, scale: Double, isRatioScale: Boolean = false) {
if (useForScale) {
maxY = max(overviewData.maxFromMaxValueFound, overviewData.maxFromMinValueFound)
minY = -maxY
}
overviewData.dsMaxScale.setMultiplier(maxY * scale / overviewData.maxFromMaxValueFound)
overviewData.dsMinScale.setMultiplier(maxY * scale / overviewData.maxFromMinValueFound)
var graphMaxY = maxY
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)
}
@ -201,7 +213,7 @@ class GraphData(
graph.gridLabelRenderer.numHorizontalLabels = 7 // only 7 because of the space
}
internal fun addSeries(s: Series<*>) = series.add(s)
private fun addSeries(s: Series<*>) = series.add(s)
fun performUpdate() {
// clear old data

View file

@ -3,10 +3,8 @@ package info.nightscout.androidaps.plugins.general.overview.graphExtensions
import android.graphics.Color
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.database.entities.TherapyEvent
import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.interfaces.Interval
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.utils.Translator
import info.nightscout.androidaps.utils.resources.ResourceHelper
@ -17,7 +15,7 @@ class TherapyEventDataPoint @Inject constructor(
private val resourceHelper: ResourceHelper,
private val profileFunction: ProfileFunction,
private val translator: Translator
) : DataPointWithLabelInterface, Interval {
) : DataPointWithLabelInterface {
private var yValue = 0.0
@ -49,10 +47,10 @@ class TherapyEventDataPoint @Inject constructor(
}
override fun getLabel(): String? =
if (data.note != null) data.note
if (data.note.isNullOrBlank().not()) data.note
else translator.translate(data.type)
override fun getDuration(): Long = end() - start()
override fun getDuration(): Long = data.duration
override fun getShape(): PointsWithLabelGraphSeries.Shape =
when {
data.type == TherapyEvent.Type.NS_MBG -> PointsWithLabelGraphSeries.Shape.MBG
@ -74,22 +72,4 @@ class TherapyEventDataPoint @Inject constructor(
TherapyEvent.Type.APS_OFFLINE -> Color.GRAY and -0x7f000001
else -> Color.GRAY
}
// Interval interface
private var cutEnd: Long? = null
override fun durationInMsec(): Long = data.duration
override fun start(): Long = data.timestamp
override fun originalEnd(): Long = data.timestamp + durationInMsec()
override fun end(): Long = cutEnd ?: originalEnd()
override fun cutEndTo(end: Long) {
cutEnd = end
}
override fun match(time: Long): Boolean = start() <= time && end() >= time
override fun before(time: Long): Boolean = end() < time
override fun after(time: Long): Boolean = start() > time
override val isInProgress: Boolean get() = match(System.currentTimeMillis())
override val isEndingEvent: Boolean get() = durationInMsec() == 0L
override val isValid: Boolean get() = data.type == TherapyEvent.Type.APS_OFFLINE
}

View file

@ -128,7 +128,7 @@ class NotificationStore @Inject constructor(
private fun deleteIntent(id: Int): PendingIntent {
val intent = Intent(context, DismissNotificationService::class.java)
intent.putExtra("alertID", id)
return PendingIntent.getService(context, id, intent, PendingIntent.FLAG_UPDATE_CURRENT)
return PendingIntent.getService(context, id, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
}
fun createNotificationChannel() {

View file

@ -171,7 +171,7 @@ class PersistentNotificationPlugin @Inject constructor(
val msgReadPendingIntent = PendingIntent.getBroadcast(context,
notificationHolder.notificationID,
msgReadIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
val msgReplyIntent = Intent()
.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
.setAction(REPLY_ACTION)
@ -181,7 +181,7 @@ class PersistentNotificationPlugin @Inject constructor(
context,
notificationHolder.notificationID,
msgReplyIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
// Build a RemoteInput for receiving voice input from devices
val remoteInput = RemoteInput.Builder(EXTRA_VOICE_REPLY).build()
// Create the UnreadConversation

View file

@ -1,13 +1,16 @@
package info.nightscout.androidaps.plugins.general.smsCommunicator
import android.os.SystemClock
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.interfaces.CommandQueueProvider
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.plugins.general.smsCommunicator.otp.OneTimePassword
import info.nightscout.androidaps.plugins.general.smsCommunicator.otp.OneTimePasswordValidationResult
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.resources.ResourceHelper
import javax.inject.Inject
@ -23,6 +26,7 @@ class AuthRequest internal constructor(
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var otp: OneTimePassword
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var commandQueue: CommandQueueProvider
private var date = 0L
private var processed = false
@ -49,6 +53,19 @@ class AuthRequest internal constructor(
}
if (dateUtil.now() - date < Constants.SMS_CONFIRM_TIMEOUT) {
processed = true
if (action.pumpCommand) {
val start = dateUtil.now()
//wait for empty queue
while (start + T.mins(3).msecs() > dateUtil.now()) {
if (commandQueue.size() == 0) break
SystemClock.sleep(100)
}
if (commandQueue.size() != 0) {
aapsLogger.debug(LTag.SMS, "Command timed out: " + requester.text)
smsCommunicatorPlugin.sendSMS(Sms(requester.phoneNumber, resourceHelper.gs(R.string.sms_timeout_while_wating)))
return
}
}
aapsLogger.debug(LTag.SMS, "Processing confirmed SMS: " + requester.text)
action.run()
return

View file

@ -1,37 +1,37 @@
package info.nightscout.androidaps.plugins.general.smsCommunicator
abstract class SmsAction : Runnable {
abstract class SmsAction(val pumpCommand: Boolean) : Runnable {
var aDouble: Double? = null
var anInteger: Int? = null
var secondInteger: Int? = null
var secondLong: Long? = null
var aString: String? = null
internal constructor()
internal constructor(aDouble: Double) {
internal constructor(pumpCommand: Boolean, aDouble: Double) : this(pumpCommand) {
this.aDouble = aDouble
}
internal constructor(aDouble: Double, secondInteger: Int) {
internal constructor(pumpCommand: Boolean, aDouble: Double, secondInteger: Int) : this(pumpCommand) {
this.aDouble = aDouble
this.secondInteger = secondInteger
}
internal constructor(aString: String, secondInteger: Int) {
internal constructor(pumpCommand: Boolean, aString: String, secondInteger: Int) : this(pumpCommand) {
this.aString = aString
this.secondInteger = secondInteger
}
internal constructor(anInteger: Int) {
internal constructor(pumpCommand: Boolean, anInteger: Int) : this(pumpCommand) {
this.anInteger = anInteger
}
internal constructor(anInteger: Int, secondInteger: Int) {
internal constructor(pumpCommand: Boolean, anInteger: Int, secondInteger: Int) : this(pumpCommand) {
this.anInteger = anInteger
this.secondInteger = secondInteger
}
internal constructor(anInteger: Int, secondLong: Long) {
internal constructor(pumpCommand: Boolean, anInteger: Int, secondLong: Long) : this(pumpCommand) {
this.anInteger = anInteger
this.secondLong = secondLong
}

View file

@ -11,25 +11,27 @@ 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.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.androidaps.data.DetailedBolusInfo
import info.nightscout.androidaps.interfaces.Profile
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.UserEntry.Action
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.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.EventRefreshOverview
import info.nightscout.androidaps.extensions.valueToUnitsString
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
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.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
@ -41,7 +43,6 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProv
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.extensions.valueToUnitsString
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.sharedPreferences.SP
@ -59,11 +60,13 @@ import javax.inject.Singleton
import kotlin.math.max
import kotlin.math.min
@OpenForTesting
@Singleton
class SmsCommunicatorPlugin @Inject constructor(
injector: HasAndroidInjector,
aapsLogger: AAPSLogger,
resourceHelper: ResourceHelper,
private val smsManager: SmsManager,
private val aapsSchedulers: AapsSchedulers,
private val sp: SP,
private val constraintChecker: ConstraintChecker,
@ -72,7 +75,7 @@ class SmsCommunicatorPlugin @Inject constructor(
private val fabricPrivacy: FabricPrivacy,
private val activePlugin: ActivePlugin,
private val commandQueue: CommandQueueProvider,
private val loopPlugin: LoopPlugin,
private val loop: Loop,
private val iobCobCalculator: IobCobCalculator,
private val xdripCalibrations: XdripCalibrations,
private var otp: OneTimePassword,
@ -94,8 +97,8 @@ class SmsCommunicatorPlugin @Inject constructor(
private val disposable = CompositeDisposable()
var allowedNumbers: MutableList<String> = ArrayList()
var messageToConfirm: AuthRequest? = null
var lastRemoteBolusTime: Long = 0
@Volatile var messageToConfirm: AuthRequest? = null
@Volatile var lastRemoteBolusTime: Long = 0
var messages = ArrayList<Sms>()
val commands = mapOf(
@ -181,7 +184,6 @@ class SmsCommunicatorPlugin @Inject constructor(
}
@Suppress("SpellCheckingInspection")
@kotlin.ExperimentalStdlibApi
override fun doWork(): Result {
val bundle = dataWorker.pickupBundle(inputData.getLong(DataWorker.STORE_KEY, -1))
?: return Result.failure(workDataOf("Error" to "missing input data"))
@ -224,7 +226,6 @@ class SmsCommunicatorPlugin @Inject constructor(
return false
}
@kotlin.ExperimentalStdlibApi
fun processSms(receivedSms: Sms) {
if (!isEnabled(PluginType.GENERAL)) {
aapsLogger.debug(LTag.SMS, "Ignoring SMS. Plugin disabled.")
@ -278,6 +279,7 @@ class SmsCommunicatorPlugin @Inject constructor(
else sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.wrongformat)))
"BOLUS" ->
if (!remoteCommandsAllowed) sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_remotecommandnotallowed)))
else if (commandQueue.bolusInQueue()) sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_another_bolus_in_queue)))
else if (divided.size == 2 && dateUtil.now() - lastRemoteBolusTime < minDistance) sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_remotebolusnotallowed)))
else if (divided.size == 2 && pump.isSuspended()) sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.pumpsuspended)))
else if (divided.size == 2 || divided.size == 3) processBOLUS(divided, receivedSms)
@ -303,9 +305,13 @@ class SmsCommunicatorPlugin @Inject constructor(
else sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.wrongformat)))
else ->
if (messageToConfirm?.requester?.phoneNumber == receivedSms.phoneNumber) {
messageToConfirm?.action(divided[0])
val execute = messageToConfirm
messageToConfirm = null
} else sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_unknowncommand)))
execute?.action(divided[0])
} else {
messageToConfirm = null
sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_unknowncommand)))
}
}
}
rxBus.send(EventSmsCommunicatorUpdateGui())
@ -336,18 +342,17 @@ class SmsCommunicatorPlugin @Inject constructor(
receivedSms.processed = true
}
@kotlin.ExperimentalStdlibApi
private fun processLOOP(divided: Array<String>, receivedSms: Sms) {
when (divided[1].uppercase(Locale.getDefault())) {
"DISABLE", "STOP" -> {
if (loopPlugin.isEnabled(PluginType.LOOP)) {
if (loop.enabled) {
val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_loopdisablereplywithcode), passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false) {
override fun run() {
uel.log(Action.LOOP_DISABLED, Sources.SMS)
loopPlugin.setPluginEnabled(PluginType.LOOP, false)
loop.enabled = false
commandQueue.cancelTempBasal(true, object : Callback() {
override fun run() {
rxBus.send(EventRefreshOverview("SMS_LOOP_STOP"))
@ -364,14 +369,14 @@ class SmsCommunicatorPlugin @Inject constructor(
}
"ENABLE", "START" -> {
if (!loopPlugin.isEnabled(PluginType.LOOP)) {
if (!loop.enabled) {
val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_loopenablereplywithcode), passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false) {
override fun run() {
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)))
rxBus.send(EventRefreshOverview("SMS_LOOP_START"))
}
@ -382,8 +387,8 @@ class SmsCommunicatorPlugin @Inject constructor(
}
"STATUS" -> {
val reply = if (loopPlugin.isEnabled(PluginType.LOOP)) {
if (loopPlugin.isSuspended) String.format(resourceHelper.gs(R.string.loopsuspendedfor), loopPlugin.minutesToEndOfSuspend())
val reply = if (loop.enabled) {
if (loop.isSuspended) String.format(resourceHelper.gs(R.string.loopsuspendedfor), loop.minutesToEndOfSuspend())
else resourceHelper.gs(R.string.smscommunicator_loopisenabled)
} else
resourceHelper.gs(R.string.loopisdisabled)
@ -395,10 +400,15 @@ class SmsCommunicatorPlugin @Inject constructor(
val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_loopresumereplywithcode), passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true) {
override fun run() {
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"))
commandQueue.cancelTempBasal(true, object : Callback() {
override fun run() {
@ -409,7 +419,6 @@ class SmsCommunicatorPlugin @Inject constructor(
}
}
})
loopPlugin.createOfflineEvent(0)
sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_loopresumed)))
}
})
@ -428,14 +437,19 @@ class SmsCommunicatorPlugin @Inject constructor(
val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_suspendreplywithcode), duration, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(duration) {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, duration) {
override fun run() {
uel.log(Action.SUSPEND, Sources.SMS)
commandQueue.cancelTempBasal(true, object : Callback() {
override fun run() {
if (result.success) {
loopPlugin.suspendTo(dateUtil.now() + anInteger() * 60L * 1000)
loopPlugin.createOfflineEvent(anInteger() * 60)
disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(anInteger().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)
})
rxBus.send(EventRefreshOverview("SMS_LOOP_SUSPENDED"))
val replyText = resourceHelper.gs(R.string.smscommunicator_loopsuspended) + " " +
resourceHelper.gs(if (result.success) R.string.smscommunicator_tempbasalcanceled else R.string.smscommunicator_tempbasalcancelfailed)
@ -456,7 +470,6 @@ class SmsCommunicatorPlugin @Inject constructor(
}
}
@kotlin.ExperimentalStdlibApi
private fun processNSCLIENT(divided: Array<String>, receivedSms: Sms) {
if (divided[1].uppercase(Locale.getDefault()) == "RESTART") {
rxBus.send(EventNSClientRestart())
@ -466,10 +479,9 @@ class SmsCommunicatorPlugin @Inject constructor(
sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.wrongformat)))
}
@kotlin.ExperimentalStdlibApi
private fun processHELP(divided: Array<String>, receivedSms: Sms) {
when {
divided.size == 1 -> {
divided.size == 1 -> {
sendSMS(Sms(receivedSms.phoneNumber, commands.keys.toString().replace("[", "").replace("]", "")))
receivedSms.processed = true
}
@ -481,7 +493,7 @@ class SmsCommunicatorPlugin @Inject constructor(
}
}
else -> sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.wrongformat)))
else -> sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.wrongformat)))
}
}
@ -504,7 +516,7 @@ class SmsCommunicatorPlugin @Inject constructor(
val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_pumpconnectwithcode), passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true) {
override fun run() {
uel.log(Action.RECONNECT, Sources.SMS)
commandQueue.cancelTempBasal(true, object : Callback() {
@ -512,10 +524,14 @@ class SmsCommunicatorPlugin @Inject constructor(
if (!result.success) {
sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_pumpconnectfail)))
} 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)))
rxBus.send(EventRefreshOverview("SMS_PUMP_START"))
loopPlugin.createOfflineEvent(0)
}
}
})
@ -533,11 +549,11 @@ class SmsCommunicatorPlugin @Inject constructor(
val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_pumpdisconnectwithcode), duration, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true) {
override fun run() {
uel.log(Action.DISCONNECT, Sources.SMS)
val profile = profileFunction.getProfile()
loopPlugin.disconnectPump(duration, profile)
val profile = profileFunction.getProfile() ?: return
loop.goToZeroTemp(duration, profile, OfflineEvent.Reason.DISCONNECT_PUMP)
rxBus.send(EventRefreshOverview("SMS_PUMP_DISCONNECT"))
sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_pumpdisconnected)))
}
@ -549,7 +565,6 @@ class SmsCommunicatorPlugin @Inject constructor(
}
}
@kotlin.ExperimentalStdlibApi
private fun processPROFILE(divided: Array<String>, receivedSms: Sms) { // load profiles
val anInterface = activePlugin.activeProfileSource
val store = anInterface.profile
@ -588,7 +603,7 @@ class SmsCommunicatorPlugin @Inject constructor(
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_profilereplywithcode), list[pIndex - 1], percentage, passCode)
receivedSms.processed = true
val finalPercentage = percentage
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(list[pIndex - 1] as String, finalPercentage) {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, list[pIndex - 1] as String, finalPercentage) {
override fun run() {
profileFunction.createProfileSwitch(store, list[pIndex - 1] as String, 0, finalPercentage, 0, dateUtil.now())
val replyText = resourceHelper.gs(R.string.profileswitchcreated)
@ -603,13 +618,12 @@ class SmsCommunicatorPlugin @Inject constructor(
receivedSms.processed = true
}
@kotlin.ExperimentalStdlibApi
private fun processBASAL(divided: Array<String>, receivedSms: Sms) {
if (divided[1].uppercase(Locale.getDefault()) == "CANCEL" || divided[1].uppercase(Locale.getDefault()) == "STOP") {
val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_basalstopreplywithcode), passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true) {
override fun run() {
commandQueue.cancelTempBasal(true, object : Callback() {
override fun run() {
@ -644,7 +658,7 @@ class SmsCommunicatorPlugin @Inject constructor(
val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_basalpctreplywithcode), tempBasalPct, duration, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(tempBasalPct, duration) {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, tempBasalPct, duration) {
override fun run() {
commandQueue.tempBasalPercent(anInteger(), secondInteger(), true, profile, PumpSync.TemporaryBasalType.NORMAL, object : Callback() {
override fun run() {
@ -688,7 +702,7 @@ class SmsCommunicatorPlugin @Inject constructor(
val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_basalreplywithcode), tempBasal, duration, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(tempBasal, duration) {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, tempBasal, duration) {
override fun run() {
commandQueue.tempBasalAbsolute(aDouble(), secondInteger(), true, profile, PumpSync.TemporaryBasalType.NORMAL, object : Callback() {
override fun run() {
@ -720,13 +734,12 @@ class SmsCommunicatorPlugin @Inject constructor(
}
}
@kotlin.ExperimentalStdlibApi
private fun processEXTENDED(divided: Array<String>, receivedSms: Sms) {
if (divided[1].uppercase(Locale.getDefault()) == "CANCEL" || divided[1].uppercase(Locale.getDefault()) == "STOP") {
val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_extendedstopreplywithcode), passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true) {
override fun run() {
commandQueue.cancelExtended(object : Callback() {
override fun run() {
@ -756,7 +769,7 @@ class SmsCommunicatorPlugin @Inject constructor(
val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_extendedreplywithcode), extended, duration, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(extended, duration) {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, extended, duration) {
override fun run() {
commandQueue.extendedBolus(aDouble(), secondInteger(), object : Callback() {
override fun run() {
@ -802,7 +815,7 @@ class SmsCommunicatorPlugin @Inject constructor(
else
String.format(resourceHelper.gs(R.string.smscommunicator_bolusreplywithcode), bolus, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(bolus) {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, bolus) {
override fun run() {
val detailedBolusInfo = DetailedBolusInfo()
detailedBolusInfo.insulin = aDouble()
@ -832,7 +845,7 @@ class SmsCommunicatorPlugin @Inject constructor(
currentProfile.units == GlucoseUnit.MMOL -> Constants.defaultEatingSoonTTmmol
else -> Constants.defaultEatingSoonTTmgdl
}
disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction(
disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = dateUtil.now(),
duration = TimeUnit.MINUTES.toMillis(eatingSoonTTDuration.toLong()),
reason = TemporaryTarget.Reason.EATING_SOON,
@ -887,7 +900,6 @@ class SmsCommunicatorPlugin @Inject constructor(
return retVal
}
@kotlin.ExperimentalStdlibApi
private fun processCARBS(divided: Array<String>, receivedSms: Sms) {
var grams = SafeParse.stringToInt(divided[1])
var time = dateUtil.now()
@ -904,7 +916,7 @@ class SmsCommunicatorPlugin @Inject constructor(
val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_carbsreplywithcode), grams, dateUtil.timeString(time), passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(grams, time) {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, grams, time) {
override fun run() {
val detailedBolusInfo = DetailedBolusInfo()
detailedBolusInfo.carbs = anInteger().toDouble()
@ -931,7 +943,6 @@ class SmsCommunicatorPlugin @Inject constructor(
}
}
@kotlin.ExperimentalStdlibApi
private fun processTARGET(divided: Array<String>, receivedSms: Sms) {
val isMeal = divided[1].equals("MEAL", ignoreCase = true)
val isActivity = divided[1].equals("ACTIVITY", ignoreCase = true)
@ -941,7 +952,7 @@ class SmsCommunicatorPlugin @Inject constructor(
val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_temptargetwithcode), divided[1].uppercase(Locale.getDefault()), passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false) {
override fun run() {
val units = profileFunction.getUnits()
var keyDuration = 0
@ -979,7 +990,7 @@ class SmsCommunicatorPlugin @Inject constructor(
var tt = sp.getDouble(keyTarget, if (units == GlucoseUnit.MMOL) defaultTargetMMOL else defaultTargetMGDL)
tt = Profile.toCurrentUnits(profileFunction, tt)
tt = if (tt > 0) tt else if (units == GlucoseUnit.MMOL) defaultTargetMMOL else defaultTargetMGDL
disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction(
disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = dateUtil.now(),
duration = TimeUnit.MINUTES.toMillis(ttDuration.toLong()),
reason = TemporaryTarget.Reason.EATING_SOON,
@ -1003,7 +1014,7 @@ class SmsCommunicatorPlugin @Inject constructor(
val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_temptargetcancel), passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false) {
override fun run() {
disposable += repository.runTransactionForResult(CancelCurrentTemporaryTargetIfAnyTransaction(dateUtil.now()))
.subscribe({ result ->
@ -1028,7 +1039,7 @@ class SmsCommunicatorPlugin @Inject constructor(
val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_stopsmswithcode), passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false) {
override fun run() {
sp.putBoolean(R.string.key_smscommunicator_remotecommandsallowed, false)
val replyText = String.format(resourceHelper.gs(R.string.smscommunicator_stoppedsms))
@ -1046,7 +1057,7 @@ class SmsCommunicatorPlugin @Inject constructor(
val passCode = generatePassCode()
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_calibrationreplywithcode), cal, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(cal) {
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false, cal) {
override fun run() {
val result = xdripCalibrations.sendIntent(aDouble!!)
val replyText =
@ -1080,7 +1091,6 @@ class SmsCommunicatorPlugin @Inject constructor(
}
fun sendSMS(sms: Sms): Boolean {
val smsManager = SmsManager.getDefault()
sms.text = stripAccents(sms.text)
try {
aapsLogger.debug(LTag.SMS, "Sending SMS to " + sms.phoneNumber + ": " + sms.text)

View file

@ -88,7 +88,7 @@ class SmsCommunicatorOtpActivity : NoSplashAppCompatActivity() {
Runnable {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("OTP Secret", otp.provisioningSecret())
clipboard.primaryClip = clip
clipboard.setPrimaryClip(clip)
ToastUtils.Long.infoToast(this, resourceHelper.gs(R.string.smscommunicator_otp_export_successful))
uel.log(Action.OTP_EXPORT, Sources.SMS)
})

View file

@ -5,6 +5,7 @@ import com.eatthepath.otp.HmacOneTimePasswordGenerator
import com.google.common.io.BaseEncoding
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
@ -15,6 +16,7 @@ import javax.crypto.spec.SecretKeySpec
import javax.inject.Inject
import javax.inject.Singleton
@OpenForTesting
@Singleton
class OneTimePassword @Inject constructor(
private val sp: SP,

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.interfaces.end
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.valueToUnits
import info.nightscout.androidaps.interfaces.*
@ -572,7 +572,7 @@ class ActionStringHandler @Inject constructor(
private fun generateTempTarget(duration: Int, low: Double, high: Double) {
if (duration != 0) {
disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction(
disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = System.currentTimeMillis(),
duration = TimeUnit.MINUTES.toMillis(duration.toLong()),
reason = TemporaryTarget.Reason.WEAR,

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