diff --git a/.gitignore b/.gitignore index 79927b784d..7dc3c7a865 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ *.apk build/ .idea/ +app/src/main/jniLibs +full/ +debug/ +release/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 73fe4b80d0..5c6309f817 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: android jdk: oraclejdk8 env: matrix: - - ANDROID_TARGET=android-23 ANDROID_ABI=x86 + - ANDROID_TARGET=android-23 ANDROID_ABI=x86 org.gradle.jvmargs=-XX:-OmitStackTraceInFastThrow android: components: - platform-tools @@ -18,7 +18,17 @@ before_install: script: # Unit Test - - ./gradlew test jacocoTestReport + - ./gradlew -Pcoverage testFullDebugUnitTest jacocoTestFullDebugUnitTestReport after_success: - - bash <(curl -s https://codecov.io/bash) \ No newline at end of file + - bash <(curl -s https://codecov.io/bash) + +before_cache: + - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock + - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ + +cache: + directories: + - $HOME/.gradle/caches/ + - $HOME/.gradle/wrapper/ + - $HOME/.android/build-cache \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore deleted file mode 100644 index 796b96d1c4..0000000000 --- a/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/app/build.gradle b/app/build.gradle index 2f22df9728..16e3bfbe71 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,15 +15,17 @@ apply plugin: "jacoco-android" apply plugin: 'com.jakewharton.butterknife' ext { - supportLibraryVersion = "27.0.2" + supportLibraryVersion = "27.1.0" ormLiteVersion = "4.46" powermockVersion = "1.7.3" dexmakerVersion = "1.2" + butterknifeVersion = "8.8.1" } repositories { maven { url 'https://maven.fabric.io/public' } + jcenter { url "https://jcenter.bintray.com/" } } def generateGitBuild = { -> @@ -47,19 +49,24 @@ def generateGitBuild = { -> return stringBuilder.toString() } +tasks.matching {it instanceof Test}.all { + testLogging.events = ["failed", "skipped", "started"] + testLogging.exceptionFormat = "full" +} + android { compileSdkVersion 27 - buildToolsVersion "${supportLibraryVersion}" defaultConfig { applicationId "info.nightscout.androidaps" minSdkVersion 21 - targetSdkVersion 23 + targetSdkVersion 25 multiDexEnabled true versionCode 1500 - version "1.58" + version "1.60e-dev" buildConfigField "String", "VERSION", '"' + version + '"' buildConfigField "String", "BUILDVERSION", generateGitBuild() + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" ndk { moduleName "BleCommandUtil" @@ -69,7 +76,7 @@ android { // TODO remove once wear dependency com.google.android.gms:play-services-wearable:7.3.0 // has been upgraded (requiring significant code changes), which currently fails release // build with a deprecation warning - //abortOnError false + abortOnError false // (disabled entirely to avoid reports on the error, which would still be displayed // and it's easy to overlook that it's ignored) checkReleaseBuilds false @@ -82,7 +89,7 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { - testCoverageEnabled true + testCoverageEnabled (project.hasProperty('coverage') ? true : false) } } productFlavors { @@ -97,21 +104,6 @@ android { buildConfigField "boolean", "APS", "true" buildConfigField "boolean", "PUMPDRIVERS", "true" buildConfigField "boolean", "NSCLIENTOLNY", "false" - buildConfigField "boolean", "CLOSEDLOOP", "true" - buildConfigField "boolean", "G5UPLOADER", "false" - buildConfigField "boolean", "PUMPCONTROL", "false" - } - openloop { - dimension "standard" - resValue "string", "app_name", "AndroidAPS" - versionName version - manifestPlaceholders = [ - appIcon: "@mipmap/blueowl" - ] - buildConfigField "boolean", "APS", "true" - buildConfigField "boolean", "PUMPDRIVERS", "true" - buildConfigField "boolean", "NSCLIENTOLNY", "false" - buildConfigField "boolean", "CLOSEDLOOP", "false" buildConfigField "boolean", "G5UPLOADER", "false" buildConfigField "boolean", "PUMPCONTROL", "false" } @@ -125,7 +117,6 @@ android { buildConfigField "boolean", "APS", "false" buildConfigField "boolean", "PUMPDRIVERS", "true" buildConfigField "boolean", "NSCLIENTOLNY", "false" - buildConfigField "boolean", "CLOSEDLOOP", "false" buildConfigField "boolean", "G5UPLOADER", "false" buildConfigField "boolean", "PUMPCONTROL", "true" } @@ -139,7 +130,6 @@ android { buildConfigField "boolean", "APS", "false" buildConfigField "boolean", "PUMPDRIVERS", "false" buildConfigField "boolean", "NSCLIENTOLNY", "true" - buildConfigField "boolean", "CLOSEDLOOP", "false" buildConfigField "boolean", "G5UPLOADER", "false" buildConfigField "boolean", "PUMPCONTROL", "false" } @@ -153,12 +143,20 @@ android { buildConfigField "boolean", "APS", "false" buildConfigField "boolean", "PUMPDRIVERS", "false" buildConfigField "boolean", "NSCLIENTOLNY", "false" - buildConfigField "boolean", "CLOSEDLOOP", "false" buildConfigField "boolean", "G5UPLOADER", "true" buildConfigField "boolean", "PUMPCONTROL", "false" } } -} + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + testOptions { + unitTests.returnDefaultValues = true + unitTests.includeAndroidResources = true + } + } allprojects { repositories { @@ -169,62 +167,100 @@ allprojects { } } +configurations { + libs +} + dependencies { wearApp project(':wear') - compile fileTree(include: ['*.jar'], dir: 'libs') - compile("com.crashlytics.sdk.android:crashlytics:2.6.7@aar") { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation("com.crashlytics.sdk.android:crashlytics:2.6.7@aar") { transitive = true; } - compile("com.crashlytics.sdk.android:answers:1.3.12@aar") { + implementation("com.crashlytics.sdk.android:answers:1.3.12@aar") { transitive = true; } + libs "MilosKozak:danars-support-lib:master@zip" - compile "com.android.support:appcompat-v7:${supportLibraryVersion}" - compile "com.android.support:support-v4:${supportLibraryVersion}" - compile "com.android.support:cardview-v7:${supportLibraryVersion}" - compile "com.android.support:recyclerview-v7:${supportLibraryVersion}" - compile "com.android.support:gridlayout-v7:${supportLibraryVersion}" - compile "com.android.support:design:${supportLibraryVersion}" - compile "com.android.support:percent:${supportLibraryVersion}" - compile "com.wdullaer:materialdatetimepicker:2.3.0" - compile "com.squareup:otto:1.3.7" - compile "com.j256.ormlite:ormlite-core:${ormLiteVersion}" - compile "com.j256.ormlite:ormlite-android:${ormLiteVersion}" - compile("com.github.tony19:logback-android-classic:1.1.1-6") { + implementation "com.android.support:appcompat-v7:${supportLibraryVersion}" + implementation "com.android.support:support-v4:${supportLibraryVersion}" + implementation "com.android.support:cardview-v7:${supportLibraryVersion}" + implementation "com.android.support:recyclerview-v7:${supportLibraryVersion}" + implementation "com.android.support:gridlayout-v7:${supportLibraryVersion}" + implementation "com.android.support:design:${supportLibraryVersion}" + implementation "com.android.support:percent:${supportLibraryVersion}" + implementation "com.wdullaer:materialdatetimepicker:2.3.0" + implementation 'com.android.support.constraint:constraint-layout:1.0.2' + implementation "com.squareup:otto:1.3.7" + implementation "com.j256.ormlite:ormlite-core:${ormLiteVersion}" + implementation "com.j256.ormlite:ormlite-android:${ormLiteVersion}" + implementation("com.github.tony19:logback-android-classic:1.1.1-6") { exclude group: "com.google.android", module: "android" } - compile "org.apache.commons:commons-lang3:3.6" - compile "org.slf4j:slf4j-api:1.7.12" - compile "com.jjoe64:graphview:4.0.1" - compile "com.joanzapata.iconify:android-iconify-fontawesome:2.1.1" - compile "com.google.android.gms:play-services-wearable:7.5.0" - compile(name: "android-edittext-validator-v1.3.4-mod", ext: "aar") - compile("com.google.android:flexbox:0.3.0") { + implementation "org.apache.commons:commons-lang3:3.6" + implementation "org.slf4j:slf4j-api:1.7.12" + implementation "com.jjoe64:graphview:4.0.1" + implementation "com.joanzapata.iconify:android-iconify-fontawesome:2.1.1" + implementation "com.google.android.gms:play-services-wearable:7.5.0" + implementation(name: "android-edittext-validator-v1.3.4-mod", ext: "aar") + implementation(name: "sightparser-release", ext: "aar") + + implementation("com.google.android:flexbox:0.3.0") { exclude group: "com.android.support" } - compile("io.socket:socket.io-client:0.8.3") { + implementation("io.socket:socket.io-client:0.8.3") { // excluding org.json which is provided by Android exclude group: "org.json", module: "json" } - compile "com.google.code.gson:gson:2.7" - compile "com.google.guava:guava:20.0" + implementation "com.google.code.gson:gson:2.7" + implementation "com.google.guava:guava:20.0" - compile "net.danlew:android.joda:2.9.9.1" + implementation "net.danlew:android.joda:2.9.9.1" + implementation "uk.com.robust-it:cloning:1.9.9" - api "com.jakewharton:butterknife:8.8.1" - annotationProcessor "com.jakewharton:butterknife-compiler:8.8.1" + implementation 'org.mozilla:rhino:1.7.7.2' - testCompile "junit:junit:4.12" - testCompile "org.json:json:20140107" - testCompile "org.mockito:mockito-core:2.7.22" - testCompile "org.powermock:powermock-api-mockito2:${powermockVersion}" - testCompile "org.powermock:powermock-module-junit4-rule-agent:${powermockVersion}" - testCompile "org.powermock:powermock-module-junit4-rule:${powermockVersion}" - testCompile "org.powermock:powermock-module-junit4:${powermockVersion}" - testCompile "joda-time:joda-time:2.9.4.2" + implementation "com.jakewharton:butterknife:${butterknifeVersion}" + annotationProcessor "com.jakewharton:butterknife-compiler:${butterknifeVersion}" - androidTestCompile "org.mockito:mockito-core:2.7.22" - androidTestCompile "com.google.dexmaker:dexmaker:${dexmakerVersion}" - androidTestCompile "com.google.dexmaker:dexmaker-mockito:${dexmakerVersion}" + testImplementation "junit:junit:4.12" + testImplementation "org.json:json:20140107" + testImplementation "org.mockito:mockito-core:2.7.22" + testImplementation "org.powermock:powermock-api-mockito2:${powermockVersion}" + testImplementation "org.powermock:powermock-module-junit4-rule-agent:${powermockVersion}" + testImplementation "org.powermock:powermock-module-junit4-rule:${powermockVersion}" + testImplementation "org.powermock:powermock-module-junit4:${powermockVersion}" + testImplementation "joda-time:joda-time:2.9.4.2" + testImplementation "com.google.truth:truth:0.39" + testImplementation 'org.robolectric:robolectric:3.8' + testImplementation "org.skyscreamer:jsonassert:1.5.0" + + androidTestImplementation "org.mockito:mockito-core:2.7.22" + androidTestImplementation "com.google.dexmaker:dexmaker:${dexmakerVersion}" + androidTestImplementation "com.google.dexmaker:dexmaker-mockito:${dexmakerVersion}" } + +task unzip(type: Copy) { + def zipPath = configurations.libs.find {it.name.startsWith("danars") } + def zipFile = file(zipPath) + def outputDir = file("${buildDir}/unpacked/dist") + + from zipTree(zipFile) + into outputDir +} + +task copyLibs(dependsOn: unzip, type: Copy) { + def src = file("${buildDir}/unpacked/dist/danars-support-lib-master") + def target = file("src/main/jniLibs/") + + from src + into target +} + +task full_clean(type: Delete) { + delete file("src/main/jniLibs") +} + +clean.dependsOn full_clean +preBuild.dependsOn copyLibs \ No newline at end of file diff --git a/app/libs/rhino-1.7.7.2.jar b/app/libs/rhino-1.7.7.2.jar deleted file mode 100644 index 4a18d33609..0000000000 Binary files a/app/libs/rhino-1.7.7.2.jar and /dev/null differ diff --git a/app/libs/sightparser-release.aar b/app/libs/sightparser-release.aar new file mode 100644 index 0000000000..0d656738d2 Binary files /dev/null and b/app/libs/sightparser-release.aar differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8290c46004..ca4bff0dd5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,9 +15,11 @@ - + + + @@ -47,7 +49,7 @@ android:theme="@style/Theme.AppCompat.Translucent" /> - + @@ -61,6 +63,7 @@ + + + + + + + + + 30m @ 0 required, zero temp will be extended to 30m instead + else if (rate > maxSafeBasal) { + rate = maxSafeBasal; + } + + var suggestedRate = round_basal(rate, profile); + if (typeof(currenttemp) !== 'undefined' && typeof(currenttemp.duration) !== 'undefined' && typeof(currenttemp.rate) !== 'undefined' && currenttemp.duration > (duration-10) && currenttemp.duration <= 120 && suggestedRate <= currenttemp.rate * 1.2 && suggestedRate >= currenttemp.rate * 0.8) { + rT.reason += " "+currenttemp.duration+"m left and " + currenttemp.rate + " ~ req " + suggestedRate + "U/hr: no temp required"; + return rT; + } + + if (suggestedRate === profile.current_basal) { + if (profile.skip_neutral_temps) { + if (typeof(currenttemp) !== 'undefined' && typeof(currenttemp.duration) !== 'undefined' && currenttemp.duration > 0) { + reason(rT, 'Suggested rate is same as profile rate, a temp basal is active, canceling current temp'); + rT.duration = 0; + rT.rate = 0; + return rT; + } else { + reason(rT, 'Suggested rate is same as profile rate, no temp basal is active, doing nothing'); + return rT; + } + } else { + reason(rT, 'Setting neutral temp basal of ' + profile.current_basal + 'U/hr'); + rT.duration = duration; + rT.rate = suggestedRate; + return rT; + } + } else { + rT.duration = duration; + rT.rate = suggestedRate; + return rT; + } +}; + +module.exports = tempBasalFunctions; \ No newline at end of file diff --git a/app/src/main/assets/OpenAPSSMB/determine-basal.js b/app/src/main/assets/OpenAPSSMB/determine-basal.js new file mode 100644 index 0000000000..84bd684cf6 --- /dev/null +++ b/app/src/main/assets/OpenAPSSMB/determine-basal.js @@ -0,0 +1,1146 @@ +/* + Determine Basal + + Released under MIT license. See the accompanying LICENSE.txt file for + full terms and conditions + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + + +var round_basal = require('../round-basal') + +// Rounds value to 'digits' decimal places +function round(value, digits) +{ + if (! digits) { digits = 0; } + var scale = Math.pow(10, digits); + return Math.round(value * scale) / scale; +} + +// we expect BG to rise or fall at the rate of BGI, +// adjusted by the rate at which BG would need to rise / +// fall to get eventualBG to target over 2 hours +function calculate_expected_delta(target_bg, eventual_bg, bgi) { + // (hours * mins_per_hour) / 5 = how many 5 minute periods in 2h = 24 + var five_min_blocks = (2 * 60) / 5; + var target_delta = target_bg - eventual_bg; + var expectedDelta = round(bgi + (target_delta / five_min_blocks), 1); + return expectedDelta; +} + + +function convert_bg(value, profile) +{ + if (profile.out_units == "mmol/L") + { + return round(value / 18, 1).toFixed(1); + } + else + { + return Math.round(value); + } +} + +var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, microBolusAllowed, reservoir_data) { + var rT = {}; //short for requestedTemp + + var deliverAt = new Date(); + + if (typeof profile === 'undefined' || typeof profile.current_basal === 'undefined') { + rT.error ='Error: could not get current basal rate'; + return rT; + } + var profile_current_basal = round_basal(profile.current_basal, profile); + var basal = profile_current_basal; + + var systemTime = new Date(); + var bgTime = new Date(glucose_status.date); + var minAgo = round( (systemTime - bgTime) / 60 / 1000 ,1); + + var bg = glucose_status.glucose; + if (bg < 39) { //Dexcom is in ??? mode or calibrating + rT.reason = "CGM is calibrating or in ??? state"; + } + if (minAgo > 12 || minAgo < -5) { // Dexcom data is too old, or way in the future + rT.reason = "If current system time "+systemTime+" is correct, then BG data is too old. The last BG data was read "+minAgo+"m ago at "+bgTime; + } + if (bg < 39 || minAgo > 12 || minAgo < -5) { + if (currenttemp.rate >= basal) { // high temp is running + rT.reason += ". Canceling high temp basal of "+currenttemp.rate; + rT.deliverAt = deliverAt; + rT.temp = 'absolute'; + rT.duration = 0; + rT.rate = 0; + return rT; + //return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + } else if ( currenttemp.rate == 0 && currenttemp.duration > 30 ) { //shorten long zero temps to 30m + rT.reason += ". Shortening " + currenttemp.duration + "m long zero temp to 30m. "; + rT.deliverAt = deliverAt; + rT.temp = 'absolute'; + rT.duration = 30; + rT.rate = 0; + return rT; + //return tempBasalFunctions.setTempBasal(0, 30, profile, rT, currenttemp); + } else { //do nothing. + rT.reason += ". Temp " + currenttemp.rate + " <= current basal " + basal + "U/hr; doing nothing. "; + return rT; + } + } + + var max_iob = profile.max_iob; // maximum amount of non-bolus IOB OpenAPS will ever deliver + + // if min and max are set, then set target to their average + var target_bg; + var min_bg; + var max_bg; + if (typeof profile.min_bg !== 'undefined') { + min_bg = profile.min_bg; + } + if (typeof profile.max_bg !== 'undefined') { + max_bg = profile.max_bg; + } + if (typeof profile.min_bg !== 'undefined' && typeof profile.max_bg !== 'undefined') { + target_bg = (profile.min_bg + profile.max_bg) / 2; + } else { + rT.error ='Error: could not determine target_bg. '; + return rT; + } + + var sensitivityRatio; + var high_temptarget_raises_sensitivity = profile.exercise_mode || profile.high_temptarget_raises_sensitivity; + var normalTarget = 100; // evaluate high/low temptarget against 100, not scheduled basal (which might change) + if ( profile.half_basal_exercise_target ) { + var halfBasalTarget = profile.half_basal_exercise_target; + } else { + var halfBasalTarget = 160; // when temptarget is 160 mg/dL, run 50% basal (120 = 75%; 140 = 60%) + // 80 mg/dL with low_temptarget_lowers_sensitivity would give 1.5x basal, but is limited to autosens_max (1.2x by default) + } + if ( high_temptarget_raises_sensitivity && profile.temptargetSet && target_bg > normalTarget + 10 + || profile.low_temptarget_lowers_sensitivity && profile.temptargetSet && target_bg < normalTarget ) { + // w/ target 100, temp target 110 = .89, 120 = 0.8, 140 = 0.67, 160 = .57, and 200 = .44 + // e.g.: Sensitivity ratio set to 0.8 based on temp target of 120; Adjusting basal from 1.65 to 1.35; ISF from 58.9 to 73.6 + //sensitivityRatio = 2/(2+(target_bg-normalTarget)/40); + var c = halfBasalTarget - normalTarget; + sensitivityRatio = c/(c+target_bg-normalTarget); + // limit sensitivityRatio to profile.autosens_max (1.2x by default) + sensitivityRatio = Math.min(sensitivityRatio, profile.autosens_max); + sensitivityRatio = round(sensitivityRatio,2); + console.error("Sensitivity ratio set to "+sensitivityRatio+" based on temp target of "+target_bg+"; "); + } else if (typeof autosens_data !== 'undefined' ) { + sensitivityRatio = autosens_data.ratio; + console.error("Autosens ratio: "+sensitivityRatio+"; "); + } + if (sensitivityRatio) { + basal = profile.current_basal * sensitivityRatio; + basal = round_basal(basal, profile); + if (basal != profile_current_basal) { + console.error("Adjusting basal from "+profile_current_basal+" to "+basal+"; "); + } else { + console.error("Basal unchanged: "+basal+"; "); + } + } + + // adjust min, max, and target BG for sensitivity, such that 50% increase in ISF raises target from 100 to 120 + if (profile.temptargetSet) { + //console.error("Temp Target set, not adjusting with autosens; "); + } else if (typeof autosens_data !== 'undefined' ) { + if ( profile.sensitivity_raises_target && autosens_data.ratio < 1 || profile.resistance_lowers_target && autosens_data.ratio > 1 ) { + // with a target of 100, default 0.7-1.2 autosens min/max range would allow a 93-117 target range + min_bg = round((min_bg - 60) / autosens_data.ratio) + 60; + max_bg = round((max_bg - 60) / autosens_data.ratio) + 60; + new_target_bg = round((target_bg - 60) / autosens_data.ratio) + 60; + // don't allow target_bg below 80 + new_target_bg = Math.max(80, new_target_bg); + if (target_bg == new_target_bg) { + console.error("target_bg unchanged: "+new_target_bg+"; "); + } else { + console.error("target_bg from "+target_bg+" to "+new_target_bg+"; "); + } + target_bg = new_target_bg; + } + } + + if (typeof iob_data === 'undefined' ) { + rT.error ='Error: iob_data undefined. '; + return rT; + } + + var iobArray = iob_data; + if (typeof(iob_data.length) && iob_data.length > 1) { + iob_data = iobArray[0]; + //console.error(JSON.stringify(iob_data[0])); + } + + if (typeof iob_data.activity === 'undefined' || typeof iob_data.iob === 'undefined' ) { + rT.error ='Error: iob_data missing some property. '; + return rT; + } + + var tick; + + if (glucose_status.delta > -0.5) { + tick = "+" + round(glucose_status.delta,0); + } else { + tick = round(glucose_status.delta,0); + } + //var minDelta = Math.min(glucose_status.delta, glucose_status.short_avgdelta, glucose_status.long_avgdelta); + var minDelta = Math.min(glucose_status.delta, glucose_status.short_avgdelta); + var minAvgDelta = Math.min(glucose_status.short_avgdelta, glucose_status.long_avgdelta); + var maxDelta = Math.max(glucose_status.delta, glucose_status.short_avgdelta, glucose_status.long_avgdelta); + + var profile_sens = round(profile.sens,1) + var sens = profile.sens; + if (typeof autosens_data !== 'undefined' ) { + sens = profile.sens / sensitivityRatio; + sens = round(sens, 1); + if (sens != profile_sens) { + console.error("ISF from "+profile_sens+" to "+sens); + } else { + console.error("ISF unchanged: "+sens); + } + //console.error(" (autosens ratio "+sensitivityRatio+")"); + } + console.error("; CR:",profile.carb_ratio); + + // compare currenttemp to iob_data.lastTemp and cancel temp if they don't match + var lastTempAge; + if (typeof iob_data.lastTemp !== 'undefined' ) { + lastTempAge = round(( new Date().getTime() - iob_data.lastTemp.date ) / 60000); // in minutes + // } ---- added to not produce errors + } else { + lastTempAge = 0; + } + //console.error("currenttemp:",currenttemp,"lastTemp:",JSON.stringify(iob_data.lastTemp),"lastTempAge:",lastTempAge,"m"); + tempModulus = (lastTempAge + currenttemp.duration) % 30; + console.error("currenttemp:",currenttemp,"lastTempAge:",lastTempAge,"m","tempModulus:",tempModulus,"m"); + rT.temp = 'absolute'; + rT.deliverAt = deliverAt; + if ( microBolusAllowed && currenttemp && iob_data.lastTemp && currenttemp.rate != iob_data.lastTemp.rate ) { + rT.reason = "Warning: currenttemp rate "+currenttemp.rate+" != lastTemp rate "+iob_data.lastTemp.rate+" from pumphistory; setting neutral temp of "+basal+"."; + return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + } + if ( currenttemp && iob_data.lastTemp && currenttemp.duration > 0 ) { + // TODO: fix this (lastTemp.duration is how long it has run; currenttemp.duration is time left + //if ( currenttemp.duration < iob_data.lastTemp.duration - 2) { + //rT.reason = "Warning: currenttemp duration "+currenttemp.duration+" << lastTemp duration "+round(iob_data.lastTemp.duration,1)+" from pumphistory; setting neutral temp of "+basal+"."; + //return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + //} + //console.error(lastTempAge, round(iob_data.lastTemp.duration,1), round(lastTempAge - iob_data.lastTemp.duration,1)); + var lastTempEnded = lastTempAge - iob_data.lastTemp.duration + if ( lastTempEnded > 5 ) { + rT.reason = "Warning: currenttemp running but lastTemp from pumphistory ended "+lastTempEnded+"m ago; setting neutral temp of "+basal+"."; + //console.error(currenttemp, round(iob_data.lastTemp,1), round(lastTempAge,1)); + return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + } + // TODO: figure out a way to do this check that doesn't fail across basal schedule boundaries + //if ( tempModulus < 25 && tempModulus > 5 ) { + //rT.reason = "Warning: currenttemp duration "+currenttemp.duration+" + lastTempAge "+lastTempAge+" isn't a multiple of 30m; setting neutral temp of "+basal+"."; + //console.error(rT.reason); + //return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + //} + } + + //calculate BG impact: the amount BG "should" be rising or falling based on insulin activity alone + var bgi = round(( -iob_data.activity * sens * 5 ), 2); + // project deviations for 30 minutes + var deviation = round( 30 / 5 * ( minDelta - bgi ) ); + // don't overreact to a big negative delta: use minAvgDelta if deviation is negative + if (deviation < 0) { + deviation = round( (30 / 5) * ( minAvgDelta - bgi ) ); + // and if deviation is still negative, use long_avgdelta + if (deviation < 0) { + deviation = round( (30 / 5) * ( glucose_status.long_avgdelta - bgi ) ); + } + } + + // calculate the naive (bolus calculator math) eventual BG based on net IOB and sensitivity + if (iob_data.iob > 0) { + var naive_eventualBG = round( bg - (iob_data.iob * sens) ); + } else { // if IOB is negative, be more conservative and use the lower of sens, profile.sens + var naive_eventualBG = round( bg - (iob_data.iob * Math.min(sens, profile.sens) ) ); + } + // and adjust it for the deviation above + var eventualBG = naive_eventualBG + deviation; + // calculate what portion of that is due to bolussnooze + //var bolusContrib = iob_data.bolussnooze * sens; + // and add it back in to get snoozeBG, plus another 50% to avoid low-temping at mealtime + //var naive_snoozeBG = round( naive_eventualBG + 1.5 * bolusContrib ); + // adjust that for deviation like we did eventualBG + //var snoozeBG = naive_snoozeBG + deviation; + + // adjust target BG range if needed to safely bring down high BG faster without causing lows + if ( bg > max_bg && profile.adv_target_adjustments && ! profile.temptargetSet ) { + // with target=100, as BG rises from 100 to 160, adjustedTarget drops from 100 to 80 + var adjustedMinBG = round(Math.max(80, min_bg - (bg - min_bg)/3 ),0); + var adjustedTargetBG =round( Math.max(80, target_bg - (bg - target_bg)/3 ),0); + var adjustedMaxBG = round(Math.max(80, max_bg - (bg - max_bg)/3 ),0); + // if eventualBG, naive_eventualBG, and target_bg aren't all above adjustedMinBG, don’t use it + //console.error("naive_eventualBG:",naive_eventualBG+", eventualBG:",eventualBG); + if (eventualBG > adjustedMinBG && naive_eventualBG > adjustedMinBG && min_bg > adjustedMinBG) { + console.error("Adjusting targets for high BG: min_bg from "+min_bg+" to "+adjustedMinBG+"; "); + min_bg = adjustedMinBG; + } else { + console.error("min_bg unchanged: "+min_bg+"; "); + } + // if eventualBG, naive_eventualBG, and target_bg aren't all above adjustedTargetBG, don’t use it + if (eventualBG > adjustedTargetBG && naive_eventualBG > adjustedTargetBG && target_bg > adjustedTargetBG) { + console.error("target_bg from "+target_bg+" to "+adjustedTargetBG+"; "); + target_bg = adjustedTargetBG; + } else { + console.error("target_bg unchanged: "+target_bg+"; "); + } + // if eventualBG, naive_eventualBG, and max_bg aren't all above adjustedMaxBG, don’t use it + if (eventualBG > adjustedMaxBG && naive_eventualBG > adjustedMaxBG && max_bg > adjustedMaxBG) { + console.error("max_bg from "+max_bg+" to "+adjustedMaxBG); + max_bg = adjustedMaxBG; + } else { + console.error("max_bg unchanged: "+max_bg); + } + } + + var expectedDelta = calculate_expected_delta(target_bg, eventualBG, bgi); + if (typeof eventualBG === 'undefined' || isNaN(eventualBG)) { + rT.error ='Error: could not calculate eventualBG. '; + return rT; + } + + // min_bg of 90 -> threshold of 65, 100 -> 70 110 -> 75, and 130 -> 85 + var threshold = min_bg - 0.5*(min_bg-40); + + //console.error(reservoir_data); + + rT = { + 'temp': 'absolute' + , 'bg': bg + , 'tick': tick + , 'eventualBG': eventualBG + //, 'snoozeBG': snoozeBG + , 'insulinReq': 0 + , 'reservoir' : reservoir_data // The expected reservoir volume at which to deliver the microbolus (the reservoir volume from right before the last pumphistory run) + , 'deliverAt' : deliverAt // The time at which the microbolus should be delivered + , 'sensitivityRatio' : sensitivityRatio // autosens ratio (fraction of normal basal) + }; + + // generate predicted future BGs based on IOB, COB, and current absorption rate + + var COBpredBGs = []; + var aCOBpredBGs = []; + var IOBpredBGs = []; + var UAMpredBGs = []; + var ZTpredBGs = []; + COBpredBGs.push(bg); + aCOBpredBGs.push(bg); + IOBpredBGs.push(bg); + ZTpredBGs.push(bg); + UAMpredBGs.push(bg); + + // enable SMB whenever we have COB or UAM is enabled + // SMB is disabled by default, unless explicitly enabled in preferences.json + var enableSMB=false; + // disable SMB when a high temptarget is set + if (! microBolusAllowed) { + console.error("SMB disabled (!microBolusAllowed)") + } else if (! profile.allowSMB_with_high_temptarget && profile.temptargetSet && target_bg > 100) { + console.error("SMB disabled due to high temptarget of",target_bg); + enableSMB=false; + // enable SMB/UAM (if enabled in preferences) while we have COB + } else if (profile.enableSMB_with_COB === true && meal_data.mealCOB) { + if (meal_data.bwCarbs) { + if (profile.A52_risk_enable) { + console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard") + enableSMB=true; + } else { + console.error("SMB not enabled for Bolus Wizard COB"); + } + } else { + console.error("SMB enabled for COB of",meal_data.mealCOB); + enableSMB=true; + } + // enable SMB/UAM (if enabled in preferences) for a full 6 hours after any carb entry + // (6 hours is defined in carbWindow in lib/meal/total.js) + } else if (profile.enableSMB_after_carbs === true && meal_data.carbs ) { + if (meal_data.bwCarbs) { + if (profile.A52_risk_enable) { + console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard") + enableSMB=true; + } else { + console.error("SMB not enabled for Bolus Wizard carbs"); + } + } else { + console.error("SMB enabled for 6h after carb entry"); + enableSMB=true; + } + // enable SMB/UAM (if enabled in preferences) if a low temptarget is set + } else if (profile.enableSMB_with_temptarget === true && (profile.temptargetSet && target_bg < 100)) { + if (meal_data.bwFound) { + if (profile.A52_risk_enable) { + console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard") + enableSMB=true; + } else { + console.error("enableSMB_with_temptarget not supported within 6h of using Bolus Wizard"); + } + } else { + console.error("SMB enabled for temptarget of",convert_bg(target_bg, profile)); + enableSMB=true; + } + // enable SMB/UAM if always-on (unless previously disabled for high temptarget) + } else if (profile.enableSMB_always === true) { + if (meal_data.bwFound) { + if (profile.A52_risk_enable === true) { + console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard") + enableSMB=true; + } else { + console.error("enableSMB_always not supported within 6h of using Bolus Wizard"); + } + } else { + console.error("SMB enabled due to enableSMB_always"); + enableSMB=true; + } + } else { + console.error("SMB disabled (no enableSMB preferences active)"); + } + // enable UAM (if enabled in preferences) if SMB is enabled + var enableUAM=(profile.enableUAM && enableSMB); + + + //console.error(meal_data); + // carb impact and duration are 0 unless changed below + var ci = 0; + var cid = 0; + // calculate current carb absorption rate, and how long to absorb all carbs + // CI = current carb impact on BG in mg/dL/5m + ci = round((minDelta - bgi),1); + uci = round((minDelta - bgi),1); + // ISF (mg/dL/U) / CR (g/U) = CSF (mg/dL/g) + if (profile.temptargetSet) { + // if temptargetSet, use unadjusted profile.sens to allow activity mode sensitivityRatio to adjust CR + var csf = profile.sens / profile.carb_ratio; + } else { + // otherwise, use autosens-adjusted sens to counteract autosens meal insulin dosing adjustments + // so that autotuned CR is still in effect even when basals and ISF are being adjusted by autosens + var csf = sens / profile.carb_ratio; + } + var maxCarbAbsorptionRate = 30; // g/h; maximum rate to assume carbs will absorb if no CI observed + // limit Carb Impact to maxCarbAbsorptionRate * csf in mg/dL per 5m + maxCI = round(maxCarbAbsorptionRate*csf*5/60,1) + if (ci > maxCI) { + console.error("Limiting carb impact from",ci,"to",maxCI,"mg/dL/5m (",maxCarbAbsorptionRate,"g/h )"); + ci = maxCI; + } + // set meal_carbimpact high enough to absorb all meal carbs over 6 hours + // total_impact (mg/dL) = CSF (mg/dL/g) * carbs (g) + //console.error(csf * meal_data.carbs); + // meal_carbimpact (mg/dL/5m) = CSF (mg/dL/g) * carbs (g) / 6 (h) * (1h/60m) * 5 (m/5m) * 2 (for linear decay) + //var meal_carbimpact = round((csf * meal_data.carbs / 6 / 60 * 5 * 2),1) + var remainingCATimeMin = 3; // h; before carb absorption starts + // adjust remainingCATime (instead of CR) for autosens + remainingCATimeMin = remainingCATimeMin / sensitivityRatio; + // 20 g/h means that anything <= 60g will get a remainingCATimeMin, 80g will get 4h, and 120g 6h + // when actual absorption ramps up it will take over from remainingCATime + var assumedCarbAbsorptionRate = 20; // g/h; maximum rate to assume carbs will absorb if no CI observed + var remainingCATime = remainingCATimeMin; // added by mike https://github.com/openaps/oref0/issues/884 + if (meal_data.carbs) { + // if carbs * assumedCarbAbsorptionRate > remainingCATimeMin, raise it + // so <= 90g is assumed to take 3h, and 120g=4h + remainingCATimeMin = Math.max(remainingCATimeMin, meal_data.mealCOB/assumedCarbAbsorptionRate); + var lastCarbAge = round(( new Date().getTime() - meal_data.lastCarbTime ) / 60000); + //console.error(meal_data.lastCarbTime, lastCarbAge); + + fractionCOBAbsorbed = ( meal_data.carbs - meal_data.mealCOB ) / meal_data.carbs; + remainingCATime = remainingCATimeMin + 1.5 * lastCarbAge/60; + remainingCATime = round(remainingCATime,1); + //console.error(fractionCOBAbsorbed, remainingCATimeAdjustment, remainingCATime) + console.error("Last carbs",lastCarbAge,"minutes ago; remainingCATime:",remainingCATime,"hours;",round(fractionCOBAbsorbed*100)+"% carbs absorbed"); + } + + // calculate the number of carbs absorbed over remainingCATime hours at current CI + // CI (mg/dL/5m) * (5m)/5 (m) * 60 (min/hr) * 4 (h) / 2 (linear decay factor) = total carb impact (mg/dL) + var totalCI = Math.max(0, ci / 5 * 60 * remainingCATime / 2); + // totalCI (mg/dL) / CSF (mg/dL/g) = total carbs absorbed (g) + var totalCA = totalCI / csf; + var remainingCarbsCap = 90; // default to 90 + var remainingCarbsFraction = 1; + if (profile.remainingCarbsCap) { remainingCarbsCap = Math.min(90,profile.remainingCarbsCap); } + if (profile.remainingCarbsFraction) { remainingCarbsFraction = Math.min(1,profile.remainingCarbsFraction); } + var remainingCarbsIgnore = 1 - remainingCarbsFraction; + var remainingCarbs = Math.max(0, meal_data.mealCOB - totalCA - meal_data.carbs*remainingCarbsIgnore); + remainingCarbs = Math.min(remainingCarbsCap,remainingCarbs); + // assume remainingCarbs will absorb in a /\ shaped bilinear curve + // peaking at remainingCATime / 2 and ending at remainingCATime hours + // area of the /\ triangle is the same as a remainingCIpeak-height rectangle out to remainingCATime/2 + // remainingCIpeak (mg/dL/5m) = remainingCarbs (g) * CSF (mg/dL/g) * 5 (m/5m) * 1h/60m / (remainingCATime/2) (h) + var remainingCIpeak = remainingCarbs * csf * 5 / 60 / (remainingCATime/2); + //console.error(profile.min_5m_carbimpact,ci,totalCI,totalCA,remainingCarbs,remainingCI,remainingCATime); + //if (meal_data.mealCOB * 3 > meal_data.carbs) { } + + // calculate peak deviation in last hour, and slope from that to current deviation + var slopeFromMaxDeviation = round(meal_data.slopeFromMaxDeviation,2); + // calculate lowest deviation in last hour, and slope from that to current deviation + var slopeFromMinDeviation = round(meal_data.slopeFromMinDeviation,2); + // assume deviations will drop back down at least at 1/3 the rate they ramped up + var slopeFromDeviations = Math.min(slopeFromMaxDeviation,-slopeFromMinDeviation/3); + //console.error(slopeFromMaxDeviation); + + aci = 10; + //5m data points = g * (1U/10g) * (40mg/dL/1U) / (mg/dL/5m) + // duration (in 5m data points) = COB (g) * CSF (mg/dL/g) / ci (mg/dL/5m) + // limit cid to remainingCATime hours: the reset goes to remainingCI + if (ci == 0) { + // avoid divide by zero + cid = 0; + } else { + cid = Math.min(remainingCATime*60/5/2,Math.max(0, meal_data.mealCOB * csf / ci )); + } + acid = Math.max(0, meal_data.mealCOB * csf / aci ); + // duration (hours) = duration (5m) * 5 / 60 * 2 (to account for linear decay) + console.error("Carb Impact:",ci,"mg/dL per 5m; CI Duration:",round(cid*5/60*2,1),"hours; remaining CI (~2h peak):",round(remainingCIpeak,1),"mg/dL per 5m"); + //console.error("Accel. Carb Impact:",aci,"mg/dL per 5m; ACI Duration:",round(acid*5/60*2,1),"hours"); + var minIOBPredBG = 999; + var minCOBPredBG = 999; + var minUAMPredBG = 999; + var minGuardBG = bg; + var minCOBGuardBG = 999; + var minUAMGuardBG = 999; + var minIOBGuardBG = 999; + var minZTGuardBG = 999; + var minPredBG; + var avgPredBG; + var IOBpredBG = eventualBG; + var maxIOBPredBG = bg; + var maxCOBPredBG = bg; + var maxUAMPredBG = bg; + //var maxPredBG = bg; + var eventualPredBG = bg; + var lastIOBpredBG; + var lastCOBpredBG; + var lastUAMpredBG; + var lastZTpredBG; + var UAMduration = 0; + var remainingCItotal = 0; + var remainingCIs = []; + var predCIs = []; + try { + iobArray.forEach(function(iobTick) { + //console.error(iobTick); + predBGI = round(( -iobTick.activity * sens * 5 ), 2); + predZTBGI = round(( -iobTick.iobWithZeroTemp.activity * sens * 5 ), 2); + // for IOBpredBGs, predicted deviation impact drops linearly from current deviation down to zero + // over 60 minutes (data points every 5m) + predDev = ci * ( 1 - Math.min(1,IOBpredBGs.length/(60/5)) ); + IOBpredBG = IOBpredBGs[IOBpredBGs.length-1] + predBGI + predDev; + // calculate predBGs with long zero temp without deviations + ZTpredBG = ZTpredBGs[ZTpredBGs.length-1] + predZTBGI; + // for COBpredBGs, predicted carb impact drops linearly from current carb impact down to zero + // eventually accounting for all carbs (if they can be absorbed over DIA) + predCI = Math.max(0, Math.max(0,ci) * ( 1 - COBpredBGs.length/Math.max(cid*2,1) ) ); + predACI = Math.max(0, Math.max(0,aci) * ( 1 - COBpredBGs.length/Math.max(acid*2,1) ) ); + // if any carbs aren't absorbed after remainingCATime hours, assume they'll absorb in a /\ shaped + // bilinear curve peaking at remainingCIpeak at remainingCATime/2 hours (remainingCATime/2*12 * 5m) + // and ending at remainingCATime h (remainingCATime*12 * 5m intervals) + var intervals = Math.min( COBpredBGs.length, (remainingCATime*12)-COBpredBGs.length ); + var remainingCI = Math.max(0, intervals / (remainingCATime/2*12) * remainingCIpeak ); + remainingCItotal += predCI+remainingCI; + remainingCIs.push(round(remainingCI,0)); + predCIs.push(round(predCI,0)); + //console.error(round(predCI,1)+"+"+round(remainingCI,1)+" "); + COBpredBG = COBpredBGs[COBpredBGs.length-1] + predBGI + Math.min(0,predDev) + predCI + remainingCI; + aCOBpredBG = aCOBpredBGs[aCOBpredBGs.length-1] + predBGI + Math.min(0,predDev) + predACI; + // for UAMpredBGs, predicted carb impact drops at slopeFromDeviations + // calculate predicted CI from UAM based on slopeFromDeviations + predUCIslope = Math.max(0, uci + ( UAMpredBGs.length*slopeFromDeviations ) ); + // if slopeFromDeviations is too flat, predicted deviation impact drops linearly from + // current deviation down to zero over 3h (data points every 5m) + predUCImax = Math.max(0, uci * ( 1 - UAMpredBGs.length/Math.max(3*60/5,1) ) ); + //console.error(predUCIslope, predUCImax); + // predicted CI from UAM is the lesser of CI based on deviationSlope or DIA + predUCI = Math.min(predUCIslope, predUCImax); + if(predUCI>0) { + //console.error(UAMpredBGs.length,slopeFromDeviations, predUCI); + UAMduration=round((UAMpredBGs.length+1)*5/60,1); + } + UAMpredBG = UAMpredBGs[UAMpredBGs.length-1] + predBGI + Math.min(0, predDev) + predUCI; + //console.error(predBGI, predCI, predUCI); + // truncate all BG predictions at 4 hours + if ( IOBpredBGs.length < 48) { IOBpredBGs.push(IOBpredBG); } + if ( COBpredBGs.length < 48) { COBpredBGs.push(COBpredBG); } + if ( aCOBpredBGs.length < 48) { aCOBpredBGs.push(aCOBpredBG); } + if ( UAMpredBGs.length < 48) { UAMpredBGs.push(UAMpredBG); } + if ( ZTpredBGs.length < 48) { ZTpredBGs.push(ZTpredBG); } + // calculate minGuardBGs without a wait from COB, UAM, IOB predBGs + if ( COBpredBG < minCOBGuardBG ) { minCOBGuardBG = round(COBpredBG); } + if ( UAMpredBG < minUAMGuardBG ) { minUAMGuardBG = round(UAMpredBG); } + if ( IOBpredBG < minIOBGuardBG ) { minIOBGuardBG = round(IOBpredBG); } + if ( ZTpredBG < minZTGuardBG ) { minZTGuardBG = round(ZTpredBG); } + + // set minPredBGs starting when currently-dosed insulin activity will peak + // look ahead 60m (regardless of insulin type) so as to be less aggressive on slower insulins + var insulinPeakTime = 60; + // add 30m to allow for insluin delivery (SMBs or temps) + insulinPeakTime = 90; + var insulinPeak5m = (insulinPeakTime/60)*12; + //console.error(insulinPeakTime, insulinPeak5m, profile.insulinPeakTime, profile.curve); + + // wait 90m before setting minIOBPredBG + if ( IOBpredBGs.length > insulinPeak5m && (IOBpredBG < minIOBPredBG) ) { minIOBPredBG = round(IOBpredBG); } + if ( IOBpredBG > maxIOBPredBG ) { maxIOBPredBG = IOBpredBG; } + // wait 85-105m before setting COB and 60m for UAM minPredBGs + if ( (cid || remainingCIpeak > 0) && COBpredBGs.length > insulinPeak5m && (COBpredBG < minCOBPredBG) ) { minCOBPredBG = round(COBpredBG); } + if ( (cid || remainingCIpeak > 0) && COBpredBG > maxIOBPredBG ) { maxCOBPredBG = COBpredBG; } + if ( enableUAM && UAMpredBGs.length > 12 && (UAMpredBG < minUAMPredBG) ) { minUAMPredBG = round(UAMpredBG); } + if ( enableUAM && UAMpredBG > maxIOBPredBG ) { maxUAMPredBG = UAMpredBG; } + }); + // set eventualBG to include effect of carbs + //console.error("PredBGs:",JSON.stringify(predBGs)); + } catch (e) { + console.error("Problem with iobArray. Optional feature Advanced Meal Assist disabled:",e); + } + if (meal_data.mealCOB) { + console.error("predCIs (mg/dL/5m):",predCIs.join(" ")); + console.error("remainingCIs: ",remainingCIs.join(" ")); + } + //,"totalCA:",round(totalCA,2),"remainingCItotal/csf+totalCA:",round(remainingCItotal/csf+totalCA,2)); + rT.predBGs = {}; + IOBpredBGs.forEach(function(p, i, theArray) { + theArray[i] = round(Math.min(401,Math.max(39,p))); + }); + for (var i=IOBpredBGs.length-1; i > 12; i--) { + if (IOBpredBGs[i-1] != IOBpredBGs[i]) { break; } + else { IOBpredBGs.pop(); } + } + rT.predBGs.IOB = IOBpredBGs; + lastIOBpredBG=round(IOBpredBGs[IOBpredBGs.length-1]); + ZTpredBGs.forEach(function(p, i, theArray) { + theArray[i] = round(Math.min(401,Math.max(39,p))); + }); + for (var i=ZTpredBGs.length-1; i > 6; i--) { + //if (ZTpredBGs[i-1] != ZTpredBGs[i]) { break; } + // stop displaying ZTpredBGs once they're rising and above target + if (ZTpredBGs[i-1] >= ZTpredBGs[i] || ZTpredBGs[i] < target_bg) { break; } + else { ZTpredBGs.pop(); } + } + rT.predBGs.ZT = ZTpredBGs; + lastZTpredBG=round(ZTpredBGs[ZTpredBGs.length-1]); + if (meal_data.mealCOB > 0) { + aCOBpredBGs.forEach(function(p, i, theArray) { + theArray[i] = round(Math.min(401,Math.max(39,p))); + }); + for (var i=aCOBpredBGs.length-1; i > 12; i--) { + if (aCOBpredBGs[i-1] != aCOBpredBGs[i]) { break; } + else { aCOBpredBGs.pop(); } + } + // disable for now. may want to add a preference to re-enable + //rT.predBGs.aCOB = aCOBpredBGs; + } + if (meal_data.mealCOB > 0 && ( ci > 0 || remainingCIpeak > 0 )) { + COBpredBGs.forEach(function(p, i, theArray) { + theArray[i] = round(Math.min(401,Math.max(39,p))); + }); + for (var i=COBpredBGs.length-1; i > 12; i--) { + if (COBpredBGs[i-1] != COBpredBGs[i]) { break; } + else { COBpredBGs.pop(); } + } + rT.predBGs.COB = COBpredBGs; + lastCOBpredBG=round(COBpredBGs[COBpredBGs.length-1]); + eventualBG = Math.max(eventualBG, round(COBpredBGs[COBpredBGs.length-1]) ); + } + if (ci > 0 || remainingCIpeak > 0) { + if (enableUAM) { + UAMpredBGs.forEach(function(p, i, theArray) { + theArray[i] = round(Math.min(401,Math.max(39,p))); + }); + for (var i=UAMpredBGs.length-1; i > 12; i--) { + if (UAMpredBGs[i-1] != UAMpredBGs[i]) { break; } + else { UAMpredBGs.pop(); } + } + rT.predBGs.UAM = UAMpredBGs; + lastUAMpredBG=round(UAMpredBGs[UAMpredBGs.length-1]); + if (UAMpredBGs[UAMpredBGs.length-1]) { + eventualBG = Math.max(eventualBG, round(UAMpredBGs[UAMpredBGs.length-1]) ); + } + } + + // set eventualBG and snoozeBG based on COB or UAM predBGs + rT.eventualBG = eventualBG; + } + + console.error("UAM Impact:",uci,"mg/dL per 5m; UAM Duration:",UAMduration,"hours"); + + + minIOBPredBG = Math.max(39,minIOBPredBG); + minCOBPredBG = Math.max(39,minCOBPredBG); + minUAMPredBG = Math.max(39,minUAMPredBG); + minPredBG = round(minIOBPredBG); + + var fractionCarbsLeft = meal_data.mealCOB/meal_data.carbs; + // if we have COB and UAM is enabled, average both + if ( minUAMPredBG < 999 && minCOBPredBG < 999 ) { + // weight COBpredBG vs. UAMpredBG based on how many carbs remain as COB + avgPredBG = round( (1-fractionCarbsLeft)*UAMpredBG + fractionCarbsLeft*COBpredBG ); + // if UAM is disabled, average IOB and COB + } else if ( minCOBPredBG < 999 ) { + avgPredBG = round( (IOBpredBG + COBpredBG)/2 ); + // if we have UAM but no COB, average IOB and UAM + } else if ( minUAMPredBG < 999 ) { + avgPredBG = round( (IOBpredBG + UAMpredBG)/2 ); + } else { + avgPredBG = round( IOBpredBG ); + } + // if avgPredBG is below minZTGuardBG, bring it up to that level + if ( minZTGuardBG > avgPredBG ) { + avgPredBG = minZTGuardBG; + } + + // if we have both minCOBGuardBG and minUAMGuardBG, blend according to fractionCarbsLeft + if ( (cid || remainingCIpeak > 0) ) { + if ( enableUAM ) { + minGuardBG = fractionCarbsLeft*minCOBGuardBG + (1-fractionCarbsLeft)*minUAMGuardBG; + } else { + minGuardBG = minCOBGuardBG; + } + } else if ( enableUAM ) { + minGuardBG = minUAMGuardBG; + } else { + minGuardBG = minIOBGuardBG; + } + minGuardBG = round(minGuardBG); + //console.error(minCOBGuardBG, minUAMGuardBG, minIOBGuardBG, minGuardBG); + + var minZTUAMPredBG = minUAMPredBG; + // if minZTGuardBG is below threshold, bring down any super-high minUAMPredBG by averaging + // this helps prevent UAM from giving too much insulin in case absorption falls off suddenly + if ( minZTGuardBG < threshold ) { + minZTUAMPredBG = (minUAMPredBG + minZTGuardBG) / 2; + // if minZTGuardBG is between threshold and target, blend in the averaging + } else if ( minZTGuardBG < target_bg ) { + // target 100, threshold 70, minZTGuardBG 85 gives 50%: (85-70) / (100-70) + var blendPct = (minZTGuardBG-threshold) / (target_bg-threshold); + var blendedMinZTGuardBG = minUAMPredBG*blendPct + minZTGuardBG*(1-blendPct); + minZTUAMPredBG = (minUAMPredBG + blendedMinZTGuardBG) / 2; + //minZTUAMPredBG = minUAMPredBG - target_bg + minZTGuardBG; + // if minUAMPredBG is below minZTGuardBG, bring minUAMPredBG up by averaging + // this allows more insulin if lastUAMPredBG is below target, but minZTGuardBG is still high + } else if ( minZTGuardBG > minUAMPredBG ) { + minZTUAMPredBG = (minUAMPredBG + minZTGuardBG) / 2; + } + minZTUAMPredBG = round(minZTUAMPredBG); + //console.error("minUAMPredBG:",minUAMPredBG,"minZTGuardBG:",minZTGuardBG,"minZTUAMPredBG:",minZTUAMPredBG); + // if any carbs have been entered recently + if (meal_data.carbs) { + // average the minIOBPredBG and minUAMPredBG if available + /* + if ( minUAMPredBG < 999 ) { + avgMinPredBG = round( (minIOBPredBG+minUAMPredBG)/2 ); + } else { + avgMinPredBG = minIOBPredBG; + } + */ + + // if UAM is disabled, use max of minIOBPredBG, minCOBPredBG + if ( ! enableUAM && minCOBPredBG < 999 ) { + minPredBG = round(Math.max(minIOBPredBG, minCOBPredBG)); + // if we have COB, use minCOBPredBG, or blendedMinPredBG if it's higher + } else if ( minCOBPredBG < 999 ) { + // calculate blendedMinPredBG based on how many carbs remain as COB + //blendedMinPredBG = fractionCarbsLeft*minCOBPredBG + (1-fractionCarbsLeft)*minUAMPredBG; + blendedMinPredBG = fractionCarbsLeft*minCOBPredBG + (1-fractionCarbsLeft)*minZTUAMPredBG; + // if blendedMinPredBG > minCOBPredBG, use that instead + minPredBG = round(Math.max(minIOBPredBG, minCOBPredBG, blendedMinPredBG)); + // if carbs have been entered, but have expired, use minUAMPredBG + } else { + //minPredBG = minUAMPredBG; + minPredBG = minZTUAMPredBG; + } + // in pure UAM mode, use the higher of minIOBPredBG,minUAMPredBG + } else if ( enableUAM ) { + //minPredBG = round(Math.max(minIOBPredBG,minUAMPredBG)); + minPredBG = round(Math.max(minIOBPredBG,minZTUAMPredBG)); + } + + // make sure minPredBG isn't higher than avgPredBG + minPredBG = Math.min( minPredBG, avgPredBG ); + + console.error("minPredBG: "+minPredBG+" minIOBPredBG: "+minIOBPredBG+" minZTGuardBG: "+minZTGuardBG); + if (minCOBPredBG < 999) { + console.error(" minCOBPredBG: "+minCOBPredBG); + } + if (minUAMPredBG < 999) { + console.error(" minUAMPredBG: "+minUAMPredBG); + } + console.error(" avgPredBG:",avgPredBG,"COB:",meal_data.mealCOB,"/",meal_data.carbs); + // But if the COB line falls off a cliff, don't trust UAM too much: + // use maxCOBPredBG if it's been set and lower than minPredBG + if ( maxCOBPredBG > bg ) { + minPredBG = Math.min(minPredBG, maxCOBPredBG); + } + + rT.COB=meal_data.mealCOB; + rT.IOB=iob_data.iob; + rT.reason="COB: " + meal_data.mealCOB + ", Dev: " + convert_bg(deviation, profile) + ", BGI: " + convert_bg(bgi, profile) + ", ISF: " + convert_bg(sens, profile) + ", CR: " + round(profile.carb_ratio, 2) + ", Target: " + convert_bg(target_bg, profile) + ", minPredBG " + convert_bg(minPredBG, profile) + ", minGuardBG " + convert_bg(minGuardBG, profile) + ", IOBpredBG " + convert_bg(lastIOBpredBG, profile); + if (lastCOBpredBG > 0) { + rT.reason += ", COBpredBG " + convert_bg(lastCOBpredBG, profile); + } + if (lastUAMpredBG > 0) { + rT.reason += ", UAMpredBG " + convert_bg(lastUAMpredBG, profile) + } + rT.reason += "; "; + //var bgUndershoot = threshold - Math.min(minGuardBG, Math.max( naive_eventualBG, eventualBG )); + // use naive_eventualBG if above 40, but switch to minGuardBG if both eventualBGs hit floor of 39 + //var carbsReqBG = Math.max( naive_eventualBG, eventualBG ); + var carbsReqBG = naive_eventualBG; + if ( carbsReqBG < 40 ) { + carbsReqBG = Math.min( minGuardBG, carbsReqBG ); + } + var bgUndershoot = threshold - carbsReqBG; + // calculate how long until COB (or IOB) predBGs drop below min_bg + var minutesAboveMinBG = 240; + var minutesAboveThreshold = 240; + if (meal_data.mealCOB > 0 && ( ci > 0 || remainingCIpeak > 0 )) { + for (var i=0; i 0.20 * bg ) { + console.error("maxDelta",convert_bg(maxDelta, profile),"> 20% of BG",convert_bg(bg, profile),"- disabling SMB"); + rT.reason += "maxDelta "+convert_bg(maxDelta, profile)+" > 20% of BG "+convert_bg(bg, profile)+": SMB disabled; "; + enableSMB = false; + } + + console.error("BG projected to remain above",convert_bg(min_bg, profile),"for",minutesAboveMinBG,"minutes"); + if ( minutesAboveThreshold < 240 || minutesAboveMinBG < 60 ) { + console.error("BG projected to remain above",convert_bg(threshold,profile),"for",minutesAboveThreshold,"minutes"); + } + // include at least minutesAboveMinBG worth of zero temps in calculating carbsReq + // always include at least 30m worth of zero temp (carbs to 80, low temp up to target) + //var zeroTempDuration = Math.max(30,minutesAboveMinBG); + var zeroTempDuration = minutesAboveThreshold; + // BG undershoot, minus effect of zero temps until hitting min_bg, converted to grams, minus COB + var zeroTempEffect = profile.current_basal*sens*zeroTempDuration/60; + // don't count the last 25% of COB against carbsReq + var COBforCarbsReq = Math.max(0, meal_data.mealCOB - 0.25*meal_data.carbs); + var carbsReq = (bgUndershoot - zeroTempEffect) / csf - COBforCarbsReq; + zeroTempEffect = round(zeroTempEffect); + carbsReq = round(carbsReq); + console.error("naive_eventualBG:",naive_eventualBG,"bgUndershoot:",bgUndershoot,"zeroTempDuration:",zeroTempDuration,"zeroTempEffect:",zeroTempEffect,"carbsReq:",carbsReq); + if ( carbsReq >= profile.carbsReqThreshold && minutesAboveThreshold <= 45 ) { + rT.carbsReq = carbsReq; + rT.reason += carbsReq + " add'l carbs req w/in " + minutesAboveThreshold + "m; "; + } + // don't low glucose suspend if IOB is already super negative and BG is rising faster than predicted + if (bg < threshold && iob_data.iob < -profile.current_basal*20/60 && minDelta > 0 && minDelta > expectedDelta) { + rT.reason += "IOB "+iob_data.iob+" < " + round(-profile.current_basal*20/60,2); + rT.reason += " and minDelta " + convert_bg(minDelta, profile) + " > " + "expectedDelta " + convert_bg(expectedDelta, profile) + "; "; + // predictive low glucose suspend mode: BG is / is projected to be < threshold + } else if ( bg < threshold || minGuardBG < threshold ) { + rT.reason += "minGuardBG " + convert_bg(minGuardBG, profile) + "<" + convert_bg(threshold, profile); + var bgUndershoot = target_bg - minGuardBG; + var worstCaseInsulinReq = bgUndershoot / sens; + var durationReq = round(60*worstCaseInsulinReq / profile.current_basal); + durationReq = round(durationReq/30)*30; + // always set a 30-120m zero temp (oref0-pump-loop will let any longer SMB zero temp run) + durationReq = Math.min(120,Math.max(30,durationReq)); + return tempBasalFunctions.setTempBasal(0, durationReq, profile, rT, currenttemp); + } + + if (eventualBG < min_bg) { // if eventual BG is below target: + rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " < " + convert_bg(min_bg, profile); + // if 5m or 30m avg BG is rising faster than expected delta + if ( minDelta > expectedDelta && minDelta > 0 && !carbsReq ) { + // if naive_eventualBG < 40, set a 30m zero temp (oref0-pump-loop will let any longer SMB zero temp run) + if (naive_eventualBG < 40) { + rT.reason += ", naive_eventualBG < 40. "; + return tempBasalFunctions.setTempBasal(0, 30, profile, rT, currenttemp); + } + if (glucose_status.delta > minDelta) { + rT.reason += ", but Delta " + convert_bg(tick, profile) + " > expectedDelta " + convert_bg(expectedDelta, profile); + } else { + rT.reason += ", but Min. Delta " + minDelta.toFixed(2) + " > Exp. Delta " + convert_bg(expectedDelta, profile); + } + if (currenttemp.duration > 15 && (round_basal(basal, profile) === round_basal(currenttemp.rate, profile))) { + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; + return rT; + } else { + rT.reason += "; setting current basal of " + basal + " as temp. "; + return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + } + } + + // calculate 30m low-temp required to get projected BG up to target + // use snoozeBG to more gradually ramp in any counteraction of the user's boluses + // multiply by 2 to low-temp faster for increased hypo safety + //var insulinReq = 2 * Math.min(0, (snoozeBG - target_bg) / sens); + var insulinReq = 2 * Math.min(0, (eventualBG - target_bg) / sens); + insulinReq = round( insulinReq , 2); + // calculate naiveInsulinReq based on naive_eventualBG + var naiveInsulinReq = Math.min(0, (naive_eventualBG - target_bg) / sens); + naiveInsulinReq = round( naiveInsulinReq , 2); + if (minDelta < 0 && minDelta > expectedDelta) { + // if we're barely falling, newinsulinReq should be barely negative + //rT.reason += ", Snooze BG " + convert_bg(snoozeBG, profile); + var newinsulinReq = round(( insulinReq * (minDelta / expectedDelta) ), 2); + //console.error("Increasing insulinReq from " + insulinReq + " to " + newinsulinReq); + insulinReq = newinsulinReq; + } + // rate required to deliver insulinReq less insulin over 30m: + var rate = basal + (2 * insulinReq); + rate = round_basal(rate, profile); + // if required temp < existing temp basal + var insulinScheduled = currenttemp.duration * (currenttemp.rate - basal) / 60; + // if current temp would deliver a lot (30% of basal) less than the required insulin, + // by both normal and naive calculations, then raise the rate + var minInsulinReq = Math.min(insulinReq,naiveInsulinReq); + if (insulinScheduled < minInsulinReq - basal*0.3) { + rT.reason += ", "+currenttemp.duration + "m@" + (currenttemp.rate).toFixed(2) + " is a lot less than needed. "; + return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp); + } + if (typeof currenttemp.rate !== 'undefined' && (currenttemp.duration > 5 && rate >= currenttemp.rate * 0.8)) { + rT.reason += ", temp " + currenttemp.rate + " ~< req " + rate + "U/hr. "; + return rT; + } else { + // calculate a long enough zero temp to eventually correct back up to target + if ( rate <=0 ) { + var bgUndershoot = target_bg - naive_eventualBG; + var worstCaseInsulinReq = bgUndershoot / sens; + var durationReq = round(60*worstCaseInsulinReq / profile.current_basal); + if (durationReq < 0) { + durationReq = 0; + // don't set a temp longer than 120 minutes + } else { + durationReq = round(durationReq/30)*30; + durationReq = Math.min(120,Math.max(0,durationReq)); + } + //console.error(durationReq); + //rT.reason += "insulinReq " + insulinReq + "; " + if (durationReq > 0) { + rT.reason += ", setting " + durationReq + "m zero temp. "; + return tempBasalFunctions.setTempBasal(rate, durationReq, profile, rT, currenttemp); + } + } else { + rT.reason += ", setting " + rate + "U/hr. "; + } + return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp); + } + } + + // if eventual BG is above min but BG is falling faster than expected Delta + if (minDelta < expectedDelta) { + // if in SMB mode, don't cancel SMB zero temp + if (! (microBolusAllowed && enableSMB)) { + if (glucose_status.delta < minDelta) { + rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " > " + convert_bg(min_bg, profile) + " but Delta " + convert_bg(tick, profile) + " < Exp. Delta " + convert_bg(expectedDelta, profile); + } else { + rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " > " + convert_bg(min_bg, profile) + " but Min. Delta " + minDelta.toFixed(2) + " < Exp. Delta " + convert_bg(expectedDelta, profile); + } + if (currenttemp.duration > 15 && (round_basal(basal, profile) === round_basal(currenttemp.rate, profile))) { + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; + return rT; + } else { + rT.reason += "; setting current basal of " + basal + " as temp. "; + return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + } + } + } + // eventualBG or minPredBG is below max_bg + if (Math.min(eventualBG,minPredBG) < max_bg) { + // if in SMB mode, don't cancel SMB zero temp + if (! (microBolusAllowed && enableSMB )) { + rT.reason += convert_bg(eventualBG, profile)+"-"+convert_bg(minPredBG, profile)+" in range: no temp required"; + if (currenttemp.duration > 15 && (round_basal(basal, profile) === round_basal(currenttemp.rate, profile))) { + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; + return rT; + } else { + rT.reason += "; setting current basal of " + basal + " as temp. "; + return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + } + } + } + + // eventual BG is at/above target + // if iob is over max, just cancel any temps + // if we're not here because of SMB, eventual BG is at/above target + if (! (microBolusAllowed && rT.COB)) { + rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " >= " + convert_bg(max_bg, profile) + ", "; + } + if (iob_data.iob > max_iob) { + rT.reason += "IOB " + round(iob_data.iob,2) + " > max_iob " + max_iob; + if (currenttemp.duration > 15 && (round_basal(basal, profile) === round_basal(currenttemp.rate, profile))) { + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; + return rT; + } else { + rT.reason += "; setting current basal of " + basal + " as temp. "; + return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + } + } else { // otherwise, calculate 30m high-temp required to get projected BG down to target + + // insulinReq is the additional insulin required to get minPredBG down to target_bg + //console.error(minPredBG,eventualBG); + //var insulinReq = round( (Math.min(minPredBG,eventualBG) - target_bg) / sens, 2); + var insulinReq = round( (Math.min(minPredBG,eventualBG) - target_bg) / sens, 2); + // when dropping, but not as fast as expected, reduce insulinReq proportionally + // to the what fraction of expectedDelta we're dropping at + //if (minDelta < 0 && minDelta > expectedDelta) { + //var newinsulinReq = round(( insulinReq * (1 - (minDelta / expectedDelta)) ), 2); + //console.error("Reducing insulinReq from " + insulinReq + " to " + newinsulinReq + " for minDelta " + minDelta + " vs. expectedDelta " + expectedDelta); + //insulinReq = newinsulinReq; + //} + // if that would put us over max_iob, then reduce accordingly + if (insulinReq > max_iob-iob_data.iob) { + rT.reason += "max_iob " + max_iob + ", "; + insulinReq = max_iob-iob_data.iob; + } + + // rate required to deliver insulinReq more insulin over 30m: + var rate = basal + (2 * insulinReq); + rate = round_basal(rate, profile); + insulinReq = round(insulinReq,3); + rT.insulinReq = insulinReq; + //console.error(iob_data.lastBolusTime); + // minutes since last bolus + var lastBolusAge = round(( new Date().getTime() - iob_data.lastBolusTime ) / 60000,1); + //console.error(lastBolusAge); + //console.error(profile.temptargetSet, target_bg, rT.COB); + // only allow microboluses with COB or low temp targets, or within DIA hours of a bolus + if (microBolusAllowed && enableSMB && bg > threshold) { + // never bolus more than maxSMBBasalMinutes worth of basal + mealInsulinReq = round( meal_data.mealCOB / profile.carb_ratio ,3); + if (typeof profile.maxSMBBasalMinutes == 'undefined' ) { + maxBolus = round( profile.current_basal * 30 / 60 ,1); + console.error("profile.maxSMBBasalMinutes undefined: defaulting to 30m"); + // if IOB covers more than COB, limit maxBolus to 30m of basal + } else if ( iob_data.iob > mealInsulinReq && iob_data.iob > 0 ) { + console.error("IOB",iob_data.iob,"> COB",meal_data.mealCOB+"; mealInsulinReq =",mealInsulinReq); + maxBolus = round( profile.current_basal * 30 / 60 ,1); + } else { + console.error("profile.maxSMBBasalMinutes:",profile.maxSMBBasalMinutes,"profile.current_basal:",profile.current_basal); + maxBolus = round( profile.current_basal * profile.maxSMBBasalMinutes / 60 ,1); + } + // bolus 1/2 the insulinReq, up to maxBolus, rounding down to nearest 0.1U + microBolus = Math.floor(Math.min(insulinReq/2,maxBolus)*10)/10; + // calculate a long enough zero temp to eventually correct back up to target + var smbTarget = target_bg; + var worstCaseInsulinReq = (smbTarget - (naive_eventualBG + minIOBPredBG)/2 ) / sens; + var durationReq = round(60*worstCaseInsulinReq / profile.current_basal); + + // if insulinReq > 0 but not enough for a microBolus, don't set an SMB zero temp + if (insulinReq > 0 && microBolus < 0.1) { + durationReq = 0; + } + + var smbLowTempReq = 0; + if (durationReq <= 0) { + durationReq = 0; + // don't set a temp longer than 120 minutes + } else if (durationReq >= 30) { + durationReq = round(durationReq/30)*30; + durationReq = Math.min(120,Math.max(0,durationReq)); + } else { + // if SMB durationReq is less than 30m, set a nonzero low temp + smbLowTempReq = round( basal * durationReq/30 ,2); + durationReq = 30; + } + rT.reason += " insulinReq " + insulinReq; + if (microBolus >= maxBolus) { + rT.reason += "; maxBolus " + maxBolus; + } + if (durationReq > 0) { + rT.reason += "; setting " + durationReq + "m low temp of " + smbLowTempReq + "U/h"; + } + rT.reason += ". "; + + //allow SMBs every 3 minutes + var nextBolusMins = round(3-lastBolusAge,1); + //console.error(naive_eventualBG, insulinReq, worstCaseInsulinReq, durationReq); + console.error("naive_eventualBG",naive_eventualBG+",",durationReq+"m "+smbLowTempReq+"U/h temp needed; last bolus",lastBolusAge+"m ago; maxBolus: "+maxBolus); + if (lastBolusAge > 3) { + if (microBolus > 0) { + rT.units = microBolus; + rT.reason += "Microbolusing " + microBolus + "U. "; + } + } else { + rT.reason += "Waiting " + nextBolusMins + "m to microbolus again. "; + } + //rT.reason += ". "; + + // if no zero temp is required, don't return yet; allow later code to set a high temp + if (durationReq > 0) { + rT.rate = smbLowTempReq; + rT.duration = durationReq; + return rT; + } + + // if insulinReq is negative, snoozeBG > target_bg, and lastCOBpredBG > target_bg, set a neutral temp + //if (insulinReq < 0 && snoozeBG > target_bg && lastCOBpredBG > target_bg) { + //rT.reason += "; SMB bolus snooze: setting current basal of " + basal + " as temp. "; + //return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + //} + } + + var maxSafeBasal = tempBasalFunctions.getMaxSafeBasal(profile); + + if (rate > maxSafeBasal) { + rT.reason += "adj. req. rate: "+rate+" to maxSafeBasal: "+maxSafeBasal+", "; + rate = round_basal(maxSafeBasal, profile); + } + + var insulinScheduled = currenttemp.duration * (currenttemp.rate - basal) / 60; + if (insulinScheduled >= insulinReq * 2) { // if current temp would deliver >2x more than the required insulin, lower the rate + rT.reason += currenttemp.duration + "m@" + (currenttemp.rate).toFixed(2) + " > 2 * insulinReq. Setting temp basal of " + rate + "U/hr. "; + return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp); + } + + if (typeof currenttemp.duration == 'undefined' || currenttemp.duration == 0) { // no temp is set + rT.reason += "no temp, setting " + rate + "U/hr. "; + return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp); + } + + if (currenttemp.duration > 5 && (round_basal(rate, profile) <= round_basal(currenttemp.rate, profile))) { // if required temp <~ existing temp basal + rT.reason += "temp " + currenttemp.rate + " >~ req " + rate + "U/hr. "; + return rT; + } + + // required temp > existing temp basal + rT.reason += "temp " + currenttemp.rate + "<" + rate + "U/hr. "; + return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp); + } + +}; + +module.exports = determine_basal; diff --git a/app/src/main/assets/logback.xml b/app/src/main/assets/logback.xml index 97e09b3e4d..d6facdb7dd 100644 --- a/app/src/main/assets/logback.xml +++ b/app/src/main/assets/logback.xml @@ -1,6 +1,6 @@ - + ${EXT_FILES_DIR}/AndroidAPS.log diff --git a/app/src/main/java/info/nightscout/androidaps/Config.java b/app/src/main/java/info/nightscout/androidaps/Config.java index 3c8d6095ed..a12e0a9ea0 100644 --- a/app/src/main/java/info/nightscout/androidaps/Config.java +++ b/app/src/main/java/info/nightscout/androidaps/Config.java @@ -13,10 +13,9 @@ public class Config { public static final boolean G5UPLOADER = BuildConfig.G5UPLOADER; public static final boolean PUMPCONTROL = BuildConfig.PUMPCONTROL; - public static final boolean DANAR = BuildConfig.PUMPDRIVERS; + public static final boolean HWPUMPS = BuildConfig.PUMPDRIVERS; public static final boolean ACTION = !BuildConfig.NSCLIENTOLNY && !BuildConfig.G5UPLOADER; - public static final boolean VIRTUALPUMP = !BuildConfig.NSCLIENTOLNY && !BuildConfig.G5UPLOADER; public static final boolean MDI = !BuildConfig.NSCLIENTOLNY && !BuildConfig.G5UPLOADER; public static final boolean OTHERPROFILES = !BuildConfig.NSCLIENTOLNY && !BuildConfig.G5UPLOADER; public static final boolean SAFETY = !BuildConfig.NSCLIENTOLNY && !BuildConfig.G5UPLOADER; @@ -31,12 +30,12 @@ public class Config { public static final boolean logPumpComm = true; public static final boolean logPrefsChange = true; public static final boolean logConfigBuilder = true; - public static final boolean logConstraintsChanges = true; public static final boolean logNSUpload = true; public static final boolean logPumpActions = true; public static final boolean logCongigBuilderActions = true; public static final boolean logAutosensData = false; public static final boolean logEvents = false; + public static final boolean logProfile = false; // DanaR specific public static final boolean logDanaBTComm = true; diff --git a/app/src/main/java/info/nightscout/androidaps/Constants.java b/app/src/main/java/info/nightscout/androidaps/Constants.java index 2d6ee910c8..56fd2b6de9 100644 --- a/app/src/main/java/info/nightscout/androidaps/Constants.java +++ b/app/src/main/java/info/nightscout/androidaps/Constants.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps; -import com.j256.ormlite.stmt.query.In; +import info.nightscout.utils.T; /** * Created by mike on 07.06.2016. @@ -14,10 +14,11 @@ public class Constants { public static final double defaultDIA = 3d; - public static final double basalAbsoluteOnlyForCheckLimit = 10101010d; - public static final Integer basalPercentOnlyForCheckLimit = 10101010; - public static final double bolusOnlyForCheckLimit = 10101010d; - public static final Integer carbsOnlyForCheckLimit = 10101010; + public static final Double REALLYHIGHBASALRATE = 1111111d; + public static final Integer REALLYHIGHPERCENTBASALRATE = 1111111; + public static final double REALLYHIGHBOLUS = 1111111d; + public static final Integer REALLYHIGHCARBS = 1111111; + public static final double REALLYHIGHIOB = 1111111d; public static final Integer notificationID = 556677; @@ -35,20 +36,20 @@ public class Constants { public static final int CPP_MIN_TIMESHIFT = -6; public static final int CPP_MAX_TIMESHIFT = 23; - // Very Hard Limits Ranges - // First value is the Lowest and second value is the Highest a Limit can define - public static final int[] VERY_HARD_LIMIT_MIN_BG = {72,180}; - public static final int[] VERY_HARD_LIMIT_MAX_BG = {90,270}; - public static final int[] VERY_HARD_LIMIT_TARGET_BG = {80,200}; - - // Very Hard Limits Ranges for Temp Targets - public static final int[] VERY_HARD_LIMIT_TEMP_MIN_BG = {72,180}; - public static final int[] VERY_HARD_LIMIT_TEMP_MAX_BG = {72,270}; - public static final int[] VERY_HARD_LIMIT_TEMP_TARGET_BG = {72,200}; - //DanaR public static final double dailyLimitWarning = 0.95d; + // Temp targets + public static final int defaultActivityTTDuration = 90; // min + public static final double defaultActivityTTmgdl = 140d; + public static final double defaultActivityTTmmol = 8d; + public static final int defaultEatingSoonTTDuration = 45; // min + public static final double defaultEatingSoonTTmgdl = 90d; + public static final double defaultEatingSoonTTmmol = 5d; + public static final int defaultHypoTTDuration = 30; // min + public static final double defaultHypoTTmgdl = 120d; + public static final double defaultHypoTTmmol = 6.5d; + //NSClientInternal public static final int MAX_LOG_LINES = 100; @@ -58,8 +59,12 @@ public class Constants { //Autosens public static final double DEVIATION_TO_BE_EQUAL = 2.0; + public static final double DEFAULT_MAX_ABSORPTION_TIME = 6.0; // Pump public static final int PUMP_MAX_CONNECTION_TIME_IN_SECONDS = 120 - 1; public static final int MIN_WATCHDOG_INTERVAL_IN_SECONDS = 12 * 60; + + //SMS Communicator + public static final long SMS_CONFIRM_TIMEOUT = T.mins(5).msecs(); } diff --git a/app/src/main/java/info/nightscout/androidaps/HistoryBrowseActivity.java b/app/src/main/java/info/nightscout/androidaps/HistoryBrowseActivity.java new file mode 100644 index 0000000000..cf3d1fc34c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/HistoryBrowseActivity.java @@ -0,0 +1,404 @@ +package info.nightscout.androidaps; + +import android.app.Activity; +import android.os.Bundle; +import android.support.v4.content.res.ResourcesCompat; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.PopupMenu; +import android.text.SpannableString; +import android.text.style.ForegroundColorSpan; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.SeekBar; +import android.widget.TextView; + +import com.jjoe64.graphview.GraphView; +import com.squareup.otto.Subscribe; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Calendar; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.OnLongClick; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.events.EventCustomCalculationFinished; +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; +import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventAutosensCalculationFinished; +import info.nightscout.androidaps.plugins.Overview.OverviewFragment; +import info.nightscout.androidaps.plugins.Overview.OverviewPlugin; +import info.nightscout.androidaps.plugins.Overview.graphData.GraphData; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.SP; + +public class HistoryBrowseActivity extends AppCompatActivity { + private static Logger log = LoggerFactory.getLogger(HistoryBrowseActivity.class); + + + ImageButton chartButton; + + boolean showBasal = true; + boolean showIob, showCob, showDev, showRat, showDevslope; + + + @BindView(R.id.historybrowse_date) + Button buttonDate; + @BindView(R.id.historybrowse_zoom) + Button buttonZoom; + @BindView(R.id.historyybrowse_bggraph) + GraphView bgGraph; + @BindView(R.id.historybrowse_iobgraph) + GraphView iobGraph; + @BindView(R.id.historybrowse_seekBar) + SeekBar seekBar; + @BindView(R.id.historybrowse_noprofile) + TextView noProfile; + + private int rangeToDisplay = 24; // for graph + private long start; + + IobCobCalculatorPlugin iobCobCalculatorPlugin; + + EventCustomCalculationFinished eventCustomCalculationFinished = new EventCustomCalculationFinished(); + + public HistoryBrowseActivity() { + iobCobCalculatorPlugin = new IobCobCalculatorPlugin(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_historybrowse); + + ButterKnife.bind(this); + + bgGraph.getGridLabelRenderer().setGridColor(MainApp.sResources.getColor(R.color.graphgrid)); + bgGraph.getGridLabelRenderer().reloadStyles(); + iobGraph.getGridLabelRenderer().setGridColor(MainApp.sResources.getColor(R.color.graphgrid)); + iobGraph.getGridLabelRenderer().reloadStyles(); + iobGraph.getGridLabelRenderer().setHorizontalLabelsVisible(false); + bgGraph.getGridLabelRenderer().setLabelVerticalWidth(50); + iobGraph.getGridLabelRenderer().setLabelVerticalWidth(50); + iobGraph.getGridLabelRenderer().setNumVerticalLabels(5); + + setupChartMenu(); + + // set start of current day + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(System.currentTimeMillis()); + calendar.set(Calendar.MILLISECOND, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.HOUR_OF_DAY, 0); + start = calendar.getTimeInMillis(); + } + + @Override + protected void onResume() { + super.onResume(); + updateGUI("onResume"); + } + + + @OnClick(R.id.historybrowse_start) + void onClickStart() { + } + + @OnClick(R.id.historybrowse_left) + void onClickLeft() { + start -= rangeToDisplay * 60 * 60 * 1000L; + updateGUI("left"); + iobCobCalculatorPlugin.clearCache(); + iobCobCalculatorPlugin.runCalculation("onClickLeft", start, true, eventCustomCalculationFinished); + } + + @OnClick(R.id.historybrowse_right) + void onClickRight() { + start += rangeToDisplay * 60 * 60 * 1000L; + updateGUI("right"); + iobCobCalculatorPlugin.clearCache(); + iobCobCalculatorPlugin.runCalculation("onClickRight", start, true, eventCustomCalculationFinished); + } + + @OnClick(R.id.historybrowse_end) + void onClickEnd() { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(System.currentTimeMillis()); + calendar.set(Calendar.MILLISECOND, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.HOUR_OF_DAY, 0); + start = calendar.getTimeInMillis(); + updateGUI("resetToMidnight"); + iobCobCalculatorPlugin.clearCache(); + iobCobCalculatorPlugin.runCalculation("onClickEnd", start, true, eventCustomCalculationFinished); + } + + @OnClick(R.id.historybrowse_zoom) + void onClickZoom() { + rangeToDisplay += 6; + rangeToDisplay = rangeToDisplay > 24 ? 6 : rangeToDisplay; + updateGUI("rangeChange"); + } + + @OnLongClick(R.id.historybrowse_zoom) + boolean onLongClickZoom() { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(start); + calendar.set(Calendar.MILLISECOND, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.HOUR_OF_DAY, 0); + start = calendar.getTimeInMillis(); + updateGUI("resetToMidnight"); + iobCobCalculatorPlugin.clearCache(); + iobCobCalculatorPlugin.runCalculation("onLongClickZoom", start, true, eventCustomCalculationFinished); + return true; + } + + @OnClick(R.id.historybrowse_date) + void onClickDate() { + } + + @Subscribe + public void onStatusEvent(final EventAutosensCalculationFinished e) { + Activity activity = this; + if (activity != null && e.cause == eventCustomCalculationFinished) { + log.debug("EventAutosensCalculationFinished"); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + updateGUI("EventAutosensCalculationFinished"); + } + }); + } + } + + void updateGUI(String from) { + final PumpInterface pump = ConfigBuilderPlugin.getActivePump(); + final Profile profile = MainApp.getConfigBuilder().getProfile(); + + if (profile == null) { + noProfile.setVisibility(View.VISIBLE); + return; + } else { + noProfile.setVisibility(View.GONE); + } + + final String units = profile.getUnits(); + + double lowLineSetting = SP.getDouble("low_mark", Profile.fromMgdlToUnits(OverviewPlugin.bgTargetLow, units)); + double highLineSetting = SP.getDouble("high_mark", Profile.fromMgdlToUnits(OverviewPlugin.bgTargetHigh, units)); + + if (lowLineSetting < 1) + lowLineSetting = Profile.fromMgdlToUnits(76d, units); + if (highLineSetting < 1) + highLineSetting = Profile.fromMgdlToUnits(180d, units); + + final double lowLine = lowLineSetting; + final double highLine = highLineSetting; + + final boolean showPrediction = false; + + int hoursToFetch; + final long toTime; + final long fromTime; + //if (showPrediction) { + //int predHours = (int) (Math.ceil(((DetermineBasalResultAMA) finalLastRun.constraintsProcessed).getLatestPredictionsTime() - System.currentTimeMillis()) / (60 * 60 * 1000)); + //predHours = Math.min(2, predHours); + //predHours = Math.max(0, predHours); + //hoursToFetch = rangeToDisplay - predHours; + //toTime = calendar.getTimeInMillis() + 100000; // little bit more to avoid wrong rounding - Graphview specific + //fromTime = toTime - hoursToFetch * 60 * 60 * 1000L; + //endTime = toTime + predHours * 60 * 60 * 1000L; + //} else { + fromTime = start + 100000; + toTime = start + rangeToDisplay * 60 * 60 * 1000L; + //} + + buttonDate.setText(DateUtil.dateAndTimeString(start)); + buttonZoom.setText(String.valueOf(rangeToDisplay)); + + log.debug("Period: " + DateUtil.dateAndTimeString(fromTime) + " - " + DateUtil.dateAndTimeString(toTime)); + + final long pointer = System.currentTimeMillis(); + + // ------------------ 1st graph + + final GraphData graphData = new GraphData(bgGraph, IobCobCalculatorPlugin.getPlugin()); + + // **** In range Area **** + graphData.addInRangeArea(fromTime, toTime, lowLine, highLine); + + // **** BG **** + if (showPrediction) +//graphData.addBgReadings(fromTime, toTime, lowLine, highLine, (DetermineBasalResultAMA) finalLastRun.constraintsProcessed); + ; + else + graphData.addBgReadings(fromTime, toTime, lowLine, highLine, null); + + // set manual x bounds to have nice steps + graphData.formatAxis(fromTime, toTime); + + // Treatments + graphData.addTreatments(fromTime, toTime); + + // add basal data + if (pump.getPumpDescription().isTempBasalCapable && showBasal) { + graphData.addBasals(fromTime, toTime, lowLine / graphData.maxY / 1.2d); + } + + // **** NOW line **** + graphData.addNowLine(pointer); + + // ------------------ 2nd graph + + final GraphData secondGraphData = new GraphData(iobGraph, iobCobCalculatorPlugin); + + boolean useIobForScale = false; + boolean useCobForScale = false; + boolean useDevForScale = false; + boolean useRatioForScale = false; + boolean useDevSlopeForScale = false; + + if (showIob) { + useIobForScale = true; + } else if (showCob) { + useCobForScale = true; + } else if (showDev) { + useDevForScale = true; + } else if (showRat) { + useRatioForScale = true; + } else if (showDevslope) { + useDevSlopeForScale = true; + } + + if (showIob) + secondGraphData.addIob(fromTime, toTime, useIobForScale, 1d); + if (showCob) + secondGraphData.addCob(fromTime, toTime, useCobForScale, useCobForScale ? 1d : 0.5d); + if (showDev) + secondGraphData.addDeviations(fromTime, toTime, useDevForScale, 1d); + if (showRat) + secondGraphData.addRatio(fromTime, toTime, useRatioForScale, 1d); + if (showDevslope) + secondGraphData.addDeviationSlope(fromTime, toTime, useDevSlopeForScale, 1d); + + // **** NOW line **** + // set manual x bounds to have nice steps + secondGraphData.formatAxis(fromTime, toTime); + secondGraphData.addNowLine(pointer); + + // do GUI update + if (showIob || showCob || showDev || showRat || showDevslope) { + iobGraph.setVisibility(View.VISIBLE); + } else { + iobGraph.setVisibility(View.GONE); + } + // finally enforce drawing of graphs + graphData.performUpdate(); + secondGraphData.performUpdate(); + } + + private void setupChartMenu() { + chartButton = (ImageButton) findViewById(R.id.overview_chartMenuButton); + chartButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + MenuItem item; + CharSequence title; + SpannableString s; + PopupMenu popup = new PopupMenu(v.getContext(), v); + + + item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.BAS.ordinal(), Menu.NONE, MainApp.gs(R.string.overview_show_basals)); + title = item.getTitle(); + s = new SpannableString(title); + s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.basal, null)), 0, s.length(), 0); + item.setTitle(s); + item.setCheckable(true); + item.setChecked(showBasal); + + item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.IOB.ordinal(), Menu.NONE, MainApp.gs(R.string.overview_show_iob)); + title = item.getTitle(); + s = new SpannableString(title); + s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.iob, null)), 0, s.length(), 0); + item.setTitle(s); + item.setCheckable(true); + item.setChecked(showIob); + + item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.COB.ordinal(), Menu.NONE, MainApp.gs(R.string.overview_show_cob)); + title = item.getTitle(); + s = new SpannableString(title); + s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.cob, null)), 0, s.length(), 0); + item.setTitle(s); + item.setCheckable(true); + item.setChecked(showCob); + + item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.DEV.ordinal(), Menu.NONE, MainApp.gs(R.string.overview_show_deviations)); + title = item.getTitle(); + s = new SpannableString(title); + s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.deviations, null)), 0, s.length(), 0); + item.setTitle(s); + item.setCheckable(true); + item.setChecked(showDev); + + item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.SEN.ordinal(), Menu.NONE, MainApp.gs(R.string.overview_show_sensitivity)); + title = item.getTitle(); + s = new SpannableString(title); + s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.ratio, null)), 0, s.length(), 0); + item.setTitle(s); + item.setCheckable(true); + item.setChecked(showRat); + + if (MainApp.devBranch) { + item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.DEVSLOPE.ordinal(), Menu.NONE, "Deviation slope"); + title = item.getTitle(); + s = new SpannableString(title); + s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.devslopepos, null)), 0, s.length(), 0); + item.setTitle(s); + item.setCheckable(true); + item.setChecked(showDevslope); + } + + popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + if (item.getItemId() == OverviewFragment.CHARTTYPE.BAS.ordinal()) { + showBasal = !item.isChecked(); + } else if (item.getItemId() == OverviewFragment.CHARTTYPE.IOB.ordinal()) { + showIob = !item.isChecked(); + } else if (item.getItemId() == OverviewFragment.CHARTTYPE.COB.ordinal()) { + showCob = !item.isChecked(); + } else if (item.getItemId() == OverviewFragment.CHARTTYPE.DEV.ordinal()) { + showDev = !item.isChecked(); + } else if (item.getItemId() == OverviewFragment.CHARTTYPE.SEN.ordinal()) { + showRat = !item.isChecked(); + } else if (item.getItemId() == OverviewFragment.CHARTTYPE.DEVSLOPE.ordinal()) { + showDevslope = !item.isChecked(); + } + updateGUI("onGraphCheckboxesCheckedChanged"); + return true; + } + }); + chartButton.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp); + popup.setOnDismissListener(new PopupMenu.OnDismissListener() { + @Override + public void onDismiss(PopupMenu menu) { + chartButton.setImageResource(R.drawable.ic_arrow_drop_down_white_24dp); + } + }); + popup.show(); + } + }); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/MainActivity.java b/app/src/main/java/info/nightscout/androidaps/MainActivity.java index e77be36ba6..440024f853 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/MainActivity.java @@ -19,6 +19,9 @@ import android.support.v4.view.ViewPager; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.PopupMenu; +import android.text.SpannableString; +import android.text.method.LinkMovementMethod; +import android.text.util.Linkify; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; @@ -27,6 +30,7 @@ import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.ImageButton; +import android.widget.TextView; import com.joanzapata.iconify.Iconify; import com.joanzapata.iconify.fonts.FontAwesomeModule; @@ -35,14 +39,16 @@ import com.squareup.otto.Subscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import info.nightscout.androidaps.Services.AlarmSoundService; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.events.EventAppExit; +import info.nightscout.androidaps.events.EventFeatureRunning; import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.events.EventRefreshGui; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.Food.FoodPlugin; import info.nightscout.androidaps.plugins.Overview.events.EventSetWakeLock; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.androidaps.tabs.SlidingTabLayout; import info.nightscout.androidaps.tabs.TabPageAdapter; import info.nightscout.utils.ImportExportPrefs; @@ -112,9 +118,9 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe runOnUiThread(new Runnable() { @Override public void run() { - if(ev.recreate) { + if (ev.recreate) { recreate(); - }else { + } else { try { // activity may be destroyed setUpTabs(true); } catch (IllegalStateException e) { @@ -171,7 +177,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe // Added in 1.57 at 21.01.2018 Integer unreachable_threshold = SP.getInt(R.string.key_pump_unreachable_threshold, 30); SP.remove(R.string.key_pump_unreachable_threshold); - if(unreachable_threshold < 30) unreachable_threshold = 30; + if (unreachable_threshold < 30) unreachable_threshold = 30; SP.putString(R.string.key_pump_unreachable_threshold, unreachable_threshold.toString()); } @@ -222,6 +228,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe super.onResume(); askForSMSPermissions(); askForLocationPermissions(); + MainApp.bus().post(new EventFeatureRunning(EventFeatureRunning.Feature.MAIN)); } @Override @@ -358,6 +365,9 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe } }, null); break; + case R.id.nav_historybrowser: + startActivity(new Intent(v.getContext(), HistoryBrowseActivity.class)); + break; case R.id.nav_resetdb: new AlertDialog.Builder(v.getContext()) .setTitle(R.string.nav_resetdb) @@ -367,6 +377,10 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe @Override public void onClick(DialogInterface dialog, int which) { MainApp.getDbHelper().resetDatabases(); + // should be handled by Plugin-Interface and + // additional service interface and plugin registry + FoodPlugin.getPlugin().getService().resetFood(); + TreatmentsPlugin.getPlugin().getService().resetTreatments(); } }) .create() @@ -386,16 +400,23 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe case R.id.nav_about: AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext()); builder.setTitle(getString(R.string.app_name) + " " + BuildConfig.VERSION); - if (Config.NSCLIENT|| Config.G5UPLOADER) + if (Config.NSCLIENT || Config.G5UPLOADER) builder.setIcon(R.mipmap.yellowowl); else builder.setIcon(R.mipmap.blueowl); String message = "Build: " + BuildConfig.BUILDVERSION + "\n"; - message += MainApp.sResources.getString(R.string.configbuilder_nightscoutversion_label) + " " + ConfigBuilderPlugin.nightscoutVersionName; - builder.setMessage(message); + message += "Flavor: " + BuildConfig.FLAVOR + BuildConfig.BUILD_TYPE + "\n"; + message += getString(R.string.configbuilder_nightscoutversion_label) + " " + ConfigBuilderPlugin.nightscoutVersionName; + if (MainApp.engineeringMode) + message += "\n" + MainApp.gs(R.string.engineering_mode_enabled); + message += getString(R.string.about_link_urls); + final SpannableString messageSpanned = new SpannableString(message); + Linkify.addLinks(messageSpanned, Linkify.WEB_URLS); + builder.setMessage(messageSpanned); builder.setPositiveButton(MainApp.sResources.getString(R.string.ok), null); AlertDialog alertDialog = builder.create(); alertDialog.show(); + ((TextView)alertDialog.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); break; case R.id.nav_exit: log.debug("Exiting"); diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index a05f1c6d0c..5456da1805 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -20,57 +20,63 @@ import net.danlew.android.joda.JodaTimeAndroid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; import java.util.ArrayList; +import ch.qos.logback.classic.LoggerContext; import info.nightscout.androidaps.Services.Intents; +import info.nightscout.androidaps.data.ConstraintChecker; import info.nightscout.androidaps.db.DatabaseHelper; -import info.nightscout.androidaps.interfaces.InsulinInterface; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.Actions.ActionsFragment; import info.nightscout.androidaps.plugins.Careportal.CareportalPlugin; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderFragment; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.ConstraintsObjectives.ObjectivesPlugin; import info.nightscout.androidaps.plugins.ConstraintsSafety.SafetyPlugin; import info.nightscout.androidaps.plugins.Food.FoodPlugin; -import info.nightscout.androidaps.plugins.Insulin.InsulinFastactingPlugin; -import info.nightscout.androidaps.plugins.Insulin.InsulinFastactingProlongedPlugin; import info.nightscout.androidaps.plugins.Insulin.InsulinOrefFreePeakPlugin; import info.nightscout.androidaps.plugins.Insulin.InsulinOrefRapidActingPlugin; import info.nightscout.androidaps.plugins.Insulin.InsulinOrefUltraRapidActingPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.Loop.LoopPlugin; -import info.nightscout.androidaps.plugins.NSClientInternal.NSClientInternalPlugin; +import info.nightscout.androidaps.plugins.NSClientInternal.NSClientPlugin; import info.nightscout.androidaps.plugins.NSClientInternal.receivers.AckAlarmReceiver; import info.nightscout.androidaps.plugins.OpenAPSAMA.OpenAPSAMAPlugin; import info.nightscout.androidaps.plugins.OpenAPSMA.OpenAPSMAPlugin; +import info.nightscout.androidaps.plugins.OpenAPSSMB.OpenAPSSMBPlugin; import info.nightscout.androidaps.plugins.Overview.OverviewPlugin; +import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.Persistentnotification.PersistentNotificationPlugin; -import info.nightscout.androidaps.plugins.ProfileCircadianPercentage.CircadianPercentageProfileFragment; import info.nightscout.androidaps.plugins.ProfileLocal.LocalProfilePlugin; import info.nightscout.androidaps.plugins.ProfileNS.NSProfilePlugin; import info.nightscout.androidaps.plugins.ProfileSimple.SimpleProfilePlugin; +import info.nightscout.androidaps.plugins.PumpCombo.ComboPlugin; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPlugin; import info.nightscout.androidaps.plugins.PumpDanaRKorean.DanaRKoreanPlugin; import info.nightscout.androidaps.plugins.PumpDanaRS.DanaRSPlugin; import info.nightscout.androidaps.plugins.PumpDanaRv2.DanaRv2Plugin; +import info.nightscout.androidaps.plugins.PumpInsight.InsightPlugin; import info.nightscout.androidaps.plugins.PumpMDI.MDIPlugin; import info.nightscout.androidaps.plugins.PumpVirtual.VirtualPumpPlugin; import info.nightscout.androidaps.plugins.SensitivityAAPS.SensitivityAAPSPlugin; import info.nightscout.androidaps.plugins.SensitivityOref0.SensitivityOref0Plugin; import info.nightscout.androidaps.plugins.SensitivityWeightedAverage.SensitivityWeightedAveragePlugin; import info.nightscout.androidaps.plugins.SmsCommunicator.SmsCommunicatorPlugin; -import info.nightscout.androidaps.plugins.SourceDexcomG5.SourceDexcomG5Plugin; -import info.nightscout.androidaps.plugins.SourceGlimp.SourceGlimpPlugin; -import info.nightscout.androidaps.plugins.SourceMM640g.SourceMM640gPlugin; -import info.nightscout.androidaps.plugins.SourceNSClient.SourceNSClientPlugin; -import info.nightscout.androidaps.plugins.SourceXdrip.SourceXdripPlugin; +import info.nightscout.androidaps.plugins.Source.SourceDexcomG5Plugin; +import info.nightscout.androidaps.plugins.Source.SourceGlimpPlugin; +import info.nightscout.androidaps.plugins.Source.SourceMM640gPlugin; +import info.nightscout.androidaps.plugins.Source.SourceNSClientPlugin; +import info.nightscout.androidaps.plugins.Source.SourceXdripPlugin; import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.androidaps.plugins.Wear.WearPlugin; import info.nightscout.androidaps.plugins.XDripStatusline.StatuslinePlugin; import info.nightscout.androidaps.receivers.DataReceiver; import info.nightscout.androidaps.receivers.KeepAliveReceiver; import info.nightscout.androidaps.receivers.NSAlarmReceiver; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.NSUpload; import io.fabric.sdk.android.Fabric; @@ -85,6 +91,7 @@ public class MainApp extends Application { private static DatabaseHelper sDatabaseHelper = null; private static ConfigBuilderPlugin sConfigBuilder = null; + private static ConstraintChecker sConstraintsChecker = null; private static ArrayList pluginsList = null; @@ -93,20 +100,39 @@ public class MainApp extends Application { private static AckAlarmReceiver ackAlarmReciever = new AckAlarmReceiver(); private LocalBroadcastManager lbm; + public static boolean devBranch; + public static boolean engineeringMode; + @Override public void onCreate() { super.onCreate(); - Fabric.with(this, new Crashlytics()); - Fabric.with(this, new Answers()); + sInstance = this; + sResources = getResources(); + sConstraintsChecker = new ConstraintChecker(this); + sDatabaseHelper = OpenHelperManager.getHelper(sInstance, DatabaseHelper.class); + + try { + if (FabricPrivacy.fabricEnabled()) { + Fabric.with(this, new Crashlytics()); + Fabric.with(this, new Answers()); + Crashlytics.setString("BUILDVERSION", BuildConfig.BUILDVERSION); + } + } catch (Exception e) { + android.util.Log.e("ANDROIDAPS", "Error with Fabric init! " + e); + } + JodaTimeAndroid.init(this); - Crashlytics.setString("BUILDVERSION", BuildConfig.BUILDVERSION); + log.info("Version: " + BuildConfig.VERSION_NAME); log.info("BuildVersion: " + BuildConfig.BUILDVERSION); - sBus = Config.logEvents ? new LoggingBus(ThreadEnforcer.ANY) : new Bus(ThreadEnforcer.ANY); + String extFilesDir = this.getLogDirectory(); + File engineeringModeSemaphore = new File(extFilesDir, "engineering_mode"); - sInstance = this; - sResources = getResources(); + engineeringMode = engineeringModeSemaphore.exists() && engineeringModeSemaphore.isFile(); + devBranch = BuildConfig.VERSION.contains("dev"); + + sBus = Config.logEvents ? new LoggingBus(ThreadEnforcer.ANY) : new Bus(ThreadEnforcer.ANY); registerLocalBroadcastReceiver(); @@ -116,29 +142,29 @@ public class MainApp extends Application { pluginsList.add(OverviewPlugin.getPlugin()); pluginsList.add(IobCobCalculatorPlugin.getPlugin()); if (Config.ACTION) pluginsList.add(ActionsFragment.getPlugin()); - pluginsList.add(InsulinFastactingPlugin.getPlugin()); - pluginsList.add(InsulinFastactingProlongedPlugin.getPlugin()); pluginsList.add(InsulinOrefRapidActingPlugin.getPlugin()); pluginsList.add(InsulinOrefUltraRapidActingPlugin.getPlugin()); pluginsList.add(InsulinOrefFreePeakPlugin.getPlugin()); pluginsList.add(SensitivityOref0Plugin.getPlugin()); pluginsList.add(SensitivityAAPSPlugin.getPlugin()); pluginsList.add(SensitivityWeightedAveragePlugin.getPlugin()); - if (Config.DANAR) pluginsList.add(DanaRPlugin.getPlugin()); - if (Config.DANAR) pluginsList.add(DanaRKoreanPlugin.getPlugin()); - if (Config.DANAR) pluginsList.add(DanaRv2Plugin.getPlugin()); - if (Config.DANAR) pluginsList.add(DanaRSPlugin.getPlugin()); + if (Config.HWPUMPS) pluginsList.add(DanaRPlugin.getPlugin()); + if (Config.HWPUMPS) pluginsList.add(DanaRKoreanPlugin.getPlugin()); + if (Config.HWPUMPS) pluginsList.add(DanaRv2Plugin.getPlugin()); + if (Config.HWPUMPS) pluginsList.add(DanaRSPlugin.getPlugin()); pluginsList.add(CareportalPlugin.getPlugin()); + if (Config.HWPUMPS && engineeringMode) + pluginsList.add(InsightPlugin.getPlugin()); // <-- Enable Insight plugin here + if (Config.HWPUMPS) pluginsList.add(ComboPlugin.getPlugin()); if (Config.MDI) pluginsList.add(MDIPlugin.getPlugin()); - if (Config.VIRTUALPUMP) pluginsList.add(VirtualPumpPlugin.getPlugin()); + pluginsList.add(VirtualPumpPlugin.getPlugin()); if (Config.APS) pluginsList.add(LoopPlugin.getPlugin()); if (Config.APS) pluginsList.add(OpenAPSMAPlugin.getPlugin()); if (Config.APS) pluginsList.add(OpenAPSAMAPlugin.getPlugin()); + if (Config.APS) pluginsList.add(OpenAPSSMBPlugin.getPlugin()); pluginsList.add(NSProfilePlugin.getPlugin()); if (Config.OTHERPROFILES) pluginsList.add(SimpleProfilePlugin.getPlugin()); if (Config.OTHERPROFILES) pluginsList.add(LocalProfilePlugin.getPlugin()); - if (Config.OTHERPROFILES) - pluginsList.add(CircadianPercentageProfileFragment.getPlugin()); pluginsList.add(TreatmentsPlugin.getPlugin()); if (Config.SAFETY) pluginsList.add(SafetyPlugin.getPlugin()); if (Config.APS) pluginsList.add(ObjectivesPlugin.getPlugin()); @@ -158,33 +184,33 @@ public class MainApp extends Application { pluginsList.add(WearPlugin.initPlugin(this)); pluginsList.add(StatuslinePlugin.initPlugin(this)); pluginsList.add(new PersistentNotificationPlugin(this)); - pluginsList.add(NSClientInternalPlugin.getPlugin()); + pluginsList.add(NSClientPlugin.getPlugin()); - pluginsList.add(sConfigBuilder = ConfigBuilderFragment.getPlugin()); + pluginsList.add(sConfigBuilder = ConfigBuilderPlugin.getPlugin()); MainApp.getConfigBuilder().initialize(); } NSUpload.uploadAppStart(); - if (Config.NSCLIENT) - Answers.getInstance().logCustom(new CustomEvent("AppStart-NSClient")); - else if (Config.G5UPLOADER) - Answers.getInstance().logCustom(new CustomEvent("AppStart-G5Uploader")); - else if (Config.PUMPCONTROL) - Answers.getInstance().logCustom(new CustomEvent("AppStart-PumpControl")); - else if (MainApp.getConfigBuilder().isClosedModeEnabled()) - Answers.getInstance().logCustom(new CustomEvent("AppStart-ClosedLoop")); - else - Answers.getInstance().logCustom(new CustomEvent("AppStart-OpenLoop")); - new Thread(new Runnable() { - @Override - public void run() { + if (Config.NSCLIENT) + FabricPrivacy.getInstance().logCustom(new CustomEvent("AppStart-NSClient")); + else if (Config.G5UPLOADER) + FabricPrivacy.getInstance().logCustom(new CustomEvent("AppStart-G5Uploader")); + else if (Config.PUMPCONTROL) + FabricPrivacy.getInstance().logCustom(new CustomEvent("AppStart-PumpControl")); + else if (MainApp.getConstraintChecker().isClosedLoopAllowed().value()) + FabricPrivacy.getInstance().logCustom(new CustomEvent("AppStart-ClosedLoop")); + else + FabricPrivacy.getInstance().logCustom(new CustomEvent("AppStart-OpenLoop")); + + final PumpInterface pump = ConfigBuilderPlugin.getActivePump(); + if (pump != null) { + new Thread(() -> { SystemClock.sleep(5000); ConfigBuilderPlugin.getCommandQueue().readStatus("Initialization", null); startKeepAliveService(); - } - }).start(); - + }).start(); + } } private void registerLocalBroadcastReceiver() { @@ -237,14 +263,15 @@ public class MainApp extends Application { return sResources.getString(id, args); } + public static int gc(int id) { + return sResources.getColor(id); + } + public static MainApp instance() { return sInstance; } public static DatabaseHelper getDbHelper() { - if (sDatabaseHelper == null) { - sDatabaseHelper = OpenHelperManager.getHelper(sInstance, DatabaseHelper.class); - } return sDatabaseHelper; } @@ -259,11 +286,15 @@ public class MainApp extends Application { return sConfigBuilder; } + public static ConstraintChecker getConstraintChecker() { + return sConstraintsChecker; + } + public static ArrayList getPluginsList() { return pluginsList; } - public static ArrayList getSpecificPluginsList(int type) { + public static ArrayList getSpecificPluginsList(PluginType type) { ArrayList newList = new ArrayList<>(); if (pluginsList != null) { @@ -277,20 +308,7 @@ public class MainApp extends Application { return newList; } - @Nullable - public static InsulinInterface getInsulinIterfaceById(int id) { - if (pluginsList != null) { - for (PluginBase p : pluginsList) { - if (p.getType() == PluginBase.INSULIN && ((InsulinInterface) p).getId() == id) - return (InsulinInterface) p; - } - } else { - log.error("InsulinInterface not found"); - } - return null; - } - - public static ArrayList getSpecificPluginsVisibleInList(int type) { + public static ArrayList getSpecificPluginsVisibleInList(PluginType type) { ArrayList newList = new ArrayList<>(); if (pluginsList != null) { @@ -319,7 +337,7 @@ public class MainApp extends Application { return newList; } - public static ArrayList getSpecificPluginsVisibleInListByInterface(Class interfaceClass, int type) { + public static ArrayList getSpecificPluginsVisibleInListByInterface(Class interfaceClass, PluginType type) { ArrayList newList = new ArrayList<>(); if (pluginsList != null) { @@ -347,9 +365,23 @@ public class MainApp extends Application { return null; } + public static boolean isEngineeringModeOrRelease() { + if (!BuildConfig.APS) + return true; + return engineeringMode || !devBranch; + } + + public String getLogDirectory() { + LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); + return lc.getProperty("EXT_FILES_DIR"); + } + @Override public void onTerminate() { super.onTerminate(); - sDatabaseHelper.close(); + if (sDatabaseHelper != null) { + sDatabaseHelper.close(); + sDatabaseHelper = null; + } } } diff --git a/app/src/main/java/info/nightscout/androidaps/PreferencesActivity.java b/app/src/main/java/info/nightscout/androidaps/PreferencesActivity.java index 5a0d84d1f7..014dfcd863 100644 --- a/app/src/main/java/info/nightscout/androidaps/PreferencesActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/PreferencesActivity.java @@ -1,11 +1,9 @@ package info.nightscout.androidaps; -import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.EditTextPreference; import android.preference.ListPreference; -import android.preference.MultiSelectListPreference; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceFragment; @@ -16,23 +14,27 @@ import android.text.TextUtils; import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.events.EventRefreshGui; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.plugins.Careportal.CareportalPlugin; import info.nightscout.androidaps.plugins.ConstraintsSafety.SafetyPlugin; import info.nightscout.androidaps.plugins.Insulin.InsulinOrefFreePeakPlugin; import info.nightscout.androidaps.plugins.Loop.LoopPlugin; -import info.nightscout.androidaps.plugins.NSClientInternal.NSClientInternalPlugin; +import info.nightscout.androidaps.plugins.NSClientInternal.NSClientPlugin; import info.nightscout.androidaps.plugins.OpenAPSAMA.OpenAPSAMAPlugin; import info.nightscout.androidaps.plugins.OpenAPSMA.OpenAPSMAPlugin; +import info.nightscout.androidaps.plugins.OpenAPSSMB.OpenAPSSMBPlugin; +import info.nightscout.androidaps.plugins.PumpCombo.ComboPlugin; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPlugin; import info.nightscout.androidaps.plugins.PumpDanaRKorean.DanaRKoreanPlugin; import info.nightscout.androidaps.plugins.PumpDanaRS.DanaRSPlugin; import info.nightscout.androidaps.plugins.PumpDanaRv2.DanaRv2Plugin; +import info.nightscout.androidaps.plugins.PumpInsight.InsightPlugin; import info.nightscout.androidaps.plugins.PumpVirtual.VirtualPumpPlugin; import info.nightscout.androidaps.plugins.SensitivityAAPS.SensitivityAAPSPlugin; import info.nightscout.androidaps.plugins.SensitivityOref0.SensitivityOref0Plugin; import info.nightscout.androidaps.plugins.SensitivityWeightedAverage.SensitivityWeightedAveragePlugin; import info.nightscout.androidaps.plugins.SmsCommunicator.SmsCommunicatorPlugin; -import info.nightscout.androidaps.plugins.SourceDexcomG5.SourceDexcomG5Plugin; +import info.nightscout.androidaps.plugins.Source.SourceDexcomG5Plugin; import info.nightscout.androidaps.plugins.Wear.WearPlugin; import info.nightscout.androidaps.plugins.XDripStatusline.StatuslinePlugin; import info.nightscout.utils.LocaleHelper; @@ -66,8 +68,8 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre if (key.equals("short_tabtitles")) { MainApp.bus().post(new EventRefreshGui()); } - if (key.equals("openapsama_useautosens") && SP.getBoolean("openapsama_useautosens", false)) { - OKDialog.show(this, MainApp.sResources.getString(R.string.configbuilder_sensitivity), MainApp.sResources.getString(R.string.sensitivity_warning), null); + if (key.equals(MainApp.gs(R.string.key_openapsama_useautosens)) && SP.getBoolean(R.string.key_openapsama_useautosens, false)) { + OKDialog.show(this, MainApp.gs(R.string.configbuilder_sensitivity), MainApp.gs(R.string.sensitivity_warning), null); } updatePrefSummary(myPreferenceFragment.getPreference(key)); } @@ -81,13 +83,13 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre EditTextPreference editTextPref = (EditTextPreference) pref; if (pref.getKey().contains("password") || pref.getKey().contains("secret")) { pref.setSummary("******"); - } else if (pref.getKey().equals(MainApp.sResources.getString(R.string.key_danars_name))) { + } else if (pref.getKey().equals(MainApp.gs(R.string.key_danars_name))) { pref.setSummary(SP.getString(R.string.key_danars_name, "")); } else if (editTextPref.getText() != null && !editTextPref.getText().equals("")) { ((EditTextPreference) pref).setDialogMessage(editTextPref.getDialogMessage()); pref.setSummary(editTextPref.getText()); } else if (pref.getKey().contains("smscommunicator_allowednumbers") && TextUtils.isEmpty(editTextPref.getText().trim())) { - pref.setSummary(MainApp.sResources.getString(R.string.smscommunicator_allowednumbers_summary)); + pref.setSummary(MainApp.gs(R.string.smscommunicator_allowednumbers_summary)); } } } @@ -112,7 +114,7 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre id = args.getInt("id"); } - void addPreferencesFromResourceIfEnabled(PluginBase p, int type) { + void addPreferencesFromResourceIfEnabled(PluginBase p, PluginType type) { if (p.isEnabled(type) && p.getPreferencesId() != -1) addPreferencesFromResource(p.getPreferencesId()); } @@ -127,60 +129,62 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre if (id != -1) { addPreferencesFromResource(id); - addPreferencesFromResource(R.xml.pref_advanced); } else { + if (!Config.NSCLIENT && !Config.G5UPLOADER) { addPreferencesFromResource(R.xml.pref_password); } addPreferencesFromResource(R.xml.pref_age); addPreferencesFromResource(R.xml.pref_language); - if (!Config.NSCLIENT && !Config.G5UPLOADER) { - addPreferencesFromResource(R.xml.pref_quickwizard); - } - addPreferencesFromResourceIfEnabled(SourceDexcomG5Plugin.getPlugin(), PluginBase.BGSOURCE); - addPreferencesFromResourceIfEnabled(CareportalPlugin.getPlugin(), PluginBase.GENERAL); - addPreferencesFromResourceIfEnabled(SafetyPlugin.getPlugin(), PluginBase.CONSTRAINTS); + addPreferencesFromResource(R.xml.pref_overview); + + addPreferencesFromResourceIfEnabled(SourceDexcomG5Plugin.getPlugin(), PluginType.BGSOURCE); + addPreferencesFromResourceIfEnabled(CareportalPlugin.getPlugin(), PluginType.GENERAL); + addPreferencesFromResourceIfEnabled(SafetyPlugin.getPlugin(), PluginType.CONSTRAINTS); if (Config.APS) { - addPreferencesFromResourceIfEnabled(LoopPlugin.getPlugin(), PluginBase.LOOP); - addPreferencesFromResourceIfEnabled(OpenAPSMAPlugin.getPlugin(), PluginBase.APS); - addPreferencesFromResourceIfEnabled(OpenAPSAMAPlugin.getPlugin(), PluginBase.APS); + addPreferencesFromResourceIfEnabled(LoopPlugin.getPlugin(), PluginType.LOOP); + addPreferencesFromResourceIfEnabled(OpenAPSMAPlugin.getPlugin(), PluginType.APS); + addPreferencesFromResourceIfEnabled(OpenAPSAMAPlugin.getPlugin(), PluginType.APS); + addPreferencesFromResourceIfEnabled(OpenAPSSMBPlugin.getPlugin(), PluginType.APS); } - addPreferencesFromResourceIfEnabled(SensitivityAAPSPlugin.getPlugin(), PluginBase.SENSITIVITY); - addPreferencesFromResourceIfEnabled(SensitivityWeightedAveragePlugin.getPlugin(), PluginBase.SENSITIVITY); - addPreferencesFromResourceIfEnabled(SensitivityOref0Plugin.getPlugin(), PluginBase.SENSITIVITY); + addPreferencesFromResourceIfEnabled(SensitivityAAPSPlugin.getPlugin(), PluginType.SENSITIVITY); + addPreferencesFromResourceIfEnabled(SensitivityWeightedAveragePlugin.getPlugin(), PluginType.SENSITIVITY); + addPreferencesFromResourceIfEnabled(SensitivityOref0Plugin.getPlugin(), PluginType.SENSITIVITY); - if (Config.DANAR) { - addPreferencesFromResourceIfEnabled(DanaRPlugin.getPlugin(), PluginBase.PUMP); - addPreferencesFromResourceIfEnabled(DanaRKoreanPlugin.getPlugin(), PluginBase.PUMP); - addPreferencesFromResourceIfEnabled(DanaRv2Plugin.getPlugin(), PluginBase.PUMP); - addPreferencesFromResourceIfEnabled(DanaRSPlugin.getPlugin(), PluginBase.PUMP); + if (Config.HWPUMPS) { + addPreferencesFromResourceIfEnabled(DanaRPlugin.getPlugin(), PluginType.PUMP); + addPreferencesFromResourceIfEnabled(DanaRKoreanPlugin.getPlugin(), PluginType.PUMP); + addPreferencesFromResourceIfEnabled(DanaRv2Plugin.getPlugin(), PluginType.PUMP); + addPreferencesFromResourceIfEnabled(DanaRSPlugin.getPlugin(), PluginType.PUMP); + addPreferencesFromResourceIfEnabled(InsightPlugin.getPlugin(), PluginType.PUMP); + addPreferencesFromResourceIfEnabled(ComboPlugin.getPlugin(), PluginType.PUMP); - if (DanaRPlugin.getPlugin().isEnabled(PluginBase.PROFILE) - || DanaRKoreanPlugin.getPlugin().isEnabled(PluginBase.PROFILE) - || DanaRv2Plugin.getPlugin().isEnabled(PluginBase.PROFILE) - || DanaRSPlugin.getPlugin().isEnabled(PluginBase.PROFILE)) { + if (DanaRPlugin.getPlugin().isEnabled(PluginType.PROFILE) + || DanaRKoreanPlugin.getPlugin().isEnabled(PluginType.PROFILE) + || DanaRv2Plugin.getPlugin().isEnabled(PluginType.PROFILE) + || DanaRSPlugin.getPlugin().isEnabled(PluginType.PROFILE)) { addPreferencesFromResource(R.xml.pref_danarprofile); } } if (!Config.NSCLIENT && !Config.G5UPLOADER) { - addPreferencesFromResourceIfEnabled(VirtualPumpPlugin.getPlugin(), PluginBase.PUMP); + addPreferencesFromResourceIfEnabled(VirtualPumpPlugin.getPlugin(), PluginType.PUMP); } - addPreferencesFromResourceIfEnabled(InsulinOrefFreePeakPlugin.getPlugin(), PluginBase.INSULIN); + addPreferencesFromResourceIfEnabled(InsulinOrefFreePeakPlugin.getPlugin(), PluginType.INSULIN); - addPreferencesFromResourceIfEnabled(NSClientInternalPlugin.getPlugin(), PluginBase.GENERAL); - addPreferencesFromResourceIfEnabled(SmsCommunicatorPlugin.getPlugin(), PluginBase.GENERAL); + addPreferencesFromResourceIfEnabled(NSClientPlugin.getPlugin(), PluginType.GENERAL); + addPreferencesFromResourceIfEnabled(SmsCommunicatorPlugin.getPlugin(), PluginType.GENERAL); if (!Config.NSCLIENT && !Config.G5UPLOADER) { addPreferencesFromResource(R.xml.pref_others); } - addPreferencesFromResource(R.xml.pref_advanced); + addPreferencesFromResource(R.xml.pref_datachoices); - addPreferencesFromResourceIfEnabled(WearPlugin.getPlugin(), PluginBase.GENERAL); - addPreferencesFromResourceIfEnabled(StatuslinePlugin.getPlugin(), PluginBase.GENERAL); + addPreferencesFromResourceIfEnabled(WearPlugin.getPlugin(), PluginType.GENERAL); + addPreferencesFromResourceIfEnabled(StatuslinePlugin.getPlugin(), PluginType.GENERAL); } initSummary(getPreferenceScreen()); diff --git a/app/src/main/java/info/nightscout/androidaps/Services/AlarmSoundService.java b/app/src/main/java/info/nightscout/androidaps/Services/AlarmSoundService.java index 60dd92a2c1..f453b04d36 100644 --- a/app/src/main/java/info/nightscout/androidaps/Services/AlarmSoundService.java +++ b/app/src/main/java/info/nightscout/androidaps/Services/AlarmSoundService.java @@ -1,8 +1,10 @@ package info.nightscout.androidaps.Services; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.content.res.AssetFileDescriptor; +import android.media.AudioManager; import android.media.MediaPlayer; import android.os.IBinder; @@ -53,7 +55,10 @@ public class AlarmSoundService extends Service { log.error("Unhandled exception", e); } player.setLooping(true); // Set looping - player.setVolume(100, 100); + AudioManager manager = (AudioManager)this.getSystemService(Context.AUDIO_SERVICE); + if (manager == null || !manager.isMusicActive()) { + player.setVolume(100, 100); + } try { player.prepare(); diff --git a/app/src/main/java/info/nightscout/androidaps/Services/DataService.java b/app/src/main/java/info/nightscout/androidaps/Services/DataService.java index b242c4c099..17dea50108 100644 --- a/app/src/main/java/info/nightscout/androidaps/Services/DataService.java +++ b/app/src/main/java/info/nightscout/androidaps/Services/DataService.java @@ -18,7 +18,8 @@ import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.CareportalEvent; -import info.nightscout.androidaps.events.EventNewBasalProfile; +import info.nightscout.androidaps.events.EventNsFood; +import info.nightscout.androidaps.events.EventNsTreatment; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.ConstraintsObjectives.ObjectivesPlugin; import info.nightscout.androidaps.plugins.NSClientInternal.data.NSDeviceStatus; @@ -33,13 +34,14 @@ import info.nightscout.androidaps.plugins.ProfileNS.NSProfilePlugin; import info.nightscout.androidaps.plugins.ProfileNS.events.EventNSProfileUpdateGUI; import info.nightscout.androidaps.plugins.PumpDanaR.activities.DanaRNSHistorySync; import info.nightscout.androidaps.plugins.SmsCommunicator.events.EventNewSMS; -import info.nightscout.androidaps.plugins.SourceDexcomG5.SourceDexcomG5Plugin; -import info.nightscout.androidaps.plugins.SourceGlimp.SourceGlimpPlugin; -import info.nightscout.androidaps.plugins.SourceMM640g.SourceMM640gPlugin; -import info.nightscout.androidaps.plugins.SourceNSClient.SourceNSClientPlugin; -import info.nightscout.androidaps.plugins.SourceXdrip.SourceXdripPlugin; +import info.nightscout.androidaps.plugins.Source.SourceDexcomG5Plugin; +import info.nightscout.androidaps.plugins.Source.SourceGlimpPlugin; +import info.nightscout.androidaps.plugins.Source.SourceMM640gPlugin; +import info.nightscout.androidaps.plugins.Source.SourceNSClientPlugin; +import info.nightscout.androidaps.plugins.Source.SourceXdripPlugin; import info.nightscout.androidaps.receivers.DataReceiver; import info.nightscout.utils.BundleLogger; +import info.nightscout.utils.JsonHelper; import info.nightscout.utils.NSUpload; import info.nightscout.utils.SP; @@ -62,32 +64,37 @@ public class DataService extends IntentService { protected void onHandleIntent(final Intent intent) { if (Config.logFunctionCalls) log.debug("onHandleIntent " + BundleLogger.log(intent.getExtras())); - - if (ConfigBuilderPlugin.getActiveBgSource().getClass().equals(SourceXdripPlugin.class)) { + if (ConfigBuilderPlugin.getPlugin().getActiveBgSource() == null) { xDripEnabled = true; nsClientEnabled = false; mm640gEnabled = false; glimpEnabled = false; dexcomG5Enabled = false; - } else if (ConfigBuilderPlugin.getActiveBgSource().getClass().equals(SourceNSClientPlugin.class)) { + } else if (ConfigBuilderPlugin.getPlugin().getActiveBgSource().getClass().equals(SourceXdripPlugin.class)) { + xDripEnabled = true; + nsClientEnabled = false; + mm640gEnabled = false; + glimpEnabled = false; + dexcomG5Enabled = false; + } else if (ConfigBuilderPlugin.getPlugin().getActiveBgSource().getClass().equals(SourceNSClientPlugin.class)) { xDripEnabled = false; nsClientEnabled = true; mm640gEnabled = false; glimpEnabled = false; dexcomG5Enabled = false; - } else if (ConfigBuilderPlugin.getActiveBgSource().getClass().equals(SourceMM640gPlugin.class)) { + } else if (ConfigBuilderPlugin.getPlugin().getActiveBgSource().getClass().equals(SourceMM640gPlugin.class)) { xDripEnabled = false; nsClientEnabled = false; mm640gEnabled = true; glimpEnabled = false; dexcomG5Enabled = false; - } else if (ConfigBuilderPlugin.getActiveBgSource().getClass().equals(SourceGlimpPlugin.class)) { + } else if (ConfigBuilderPlugin.getPlugin().getActiveBgSource().getClass().equals(SourceGlimpPlugin.class)) { xDripEnabled = false; nsClientEnabled = false; mm640gEnabled = false; glimpEnabled = true; dexcomG5Enabled = false; - } else if (ConfigBuilderPlugin.getActiveBgSource().getClass().equals(SourceDexcomG5Plugin.class)) { + } else if (ConfigBuilderPlugin.getPlugin().getActiveBgSource().getClass().equals(SourceDexcomG5Plugin.class)) { xDripEnabled = false; nsClientEnabled = false; mm640gEnabled = false; @@ -95,7 +102,7 @@ public class DataService extends IntentService { dexcomG5Enabled = true; } - boolean isNSProfile = ConfigBuilderPlugin.getActiveProfileInterface().getClass().equals(NSProfilePlugin.class); + boolean isNSProfile = MainApp.getConfigBuilder().getActiveProfileInterface() != null && MainApp.getConfigBuilder().getActiveProfileInterface().getClass().equals(NSProfilePlugin.class); boolean acceptNSData = !SP.getBoolean(R.string.key_ns_upload_only, false); Bundle bundles = intent.getExtras(); @@ -123,8 +130,8 @@ public class DataService extends IntentService { handleNewDataFromDexcomG5(intent); } } else if (Intents.ACTION_NEW_SGV.equals(action)) { - // always backfill SGV from NS - handleNewDataFromNSClient(intent); + if (nsClientEnabled || SP.getBoolean(R.string.key_ns_autobackfill, true)) + handleNewDataFromNSClient(intent); // Objectives 0 ObjectivesPlugin.bgIsAvailableInNS = true; ObjectivesPlugin.saveProgress(); @@ -190,7 +197,8 @@ public class DataService extends IntentService { bgReading.direction = bundle.getString(Intents.EXTRA_BG_SLOPE_NAME); bgReading.date = bundle.getLong(Intents.EXTRA_TIMESTAMP); bgReading.raw = bundle.getDouble(Intents.EXTRA_RAW); - + String source = bundle.getString(Intents.XDRIP_DATA_SOURCE_DESCRIPTION, "no Source specified"); + SourceXdripPlugin.getPlugin().setSource(source); MainApp.getDbHelper().createIfNotExists(bgReading, "XDRIP"); } @@ -222,7 +230,7 @@ public class DataService extends IntentService { try { JSONArray jsonArray = new JSONArray(data); log.debug("Received Dexcom Data size:" + jsonArray.length()); - for(int i = 0; i < jsonArray.length(); i++) { + for (int i = 0; i < jsonArray.length(); i++) { JSONObject json = jsonArray.getJSONObject(i); bgReading.value = json.getInt("m_value"); bgReading.direction = json.getString("m_trend"); @@ -365,11 +373,8 @@ public class DataService extends IntentService { String activeProfile = bundles.getString("activeprofile"); String profile = bundles.getString("profile"); ProfileStore profileStore = new ProfileStore(new JSONObject(profile)); - NSProfilePlugin.storeNewProfile(profileStore); + NSProfilePlugin.getPlugin().storeNewProfile(profileStore); MainApp.bus().post(new EventNSProfileUpdateGUI()); - // if there are no profile switches this should lead to profile update - if (MainApp.getConfigBuilder().getProfileSwitchesFromHistory().size() == 0) - MainApp.bus().post(new EventNewBasalProfile()); if (Config.logIncommingData) log.debug("Received profileStore: " + activeProfile + " " + profile); } catch (JSONException e) { @@ -380,19 +385,18 @@ public class DataService extends IntentService { if (intent.getAction().equals(Intents.ACTION_NEW_TREATMENT) || intent.getAction().equals(Intents.ACTION_CHANGED_TREATMENT)) { try { if (bundles.containsKey("treatment")) { - String trstring = bundles.getString("treatment"); - handleAddChangeDataFromNS(trstring); + JSONObject json = new JSONObject(bundles.getString("treatment")); + handleTreatmentFromNS(json, intent); } if (bundles.containsKey("treatments")) { String trstring = bundles.getString("treatments"); JSONArray jsonArray = new JSONArray(trstring); for (int i = 0; i < jsonArray.length(); i++) { - JSONObject trJson = jsonArray.getJSONObject(i); - String trstr = trJson.toString(); - handleAddChangeDataFromNS(trstr); + JSONObject json = jsonArray.getJSONObject(i); + handleTreatmentFromNS(json, intent); } } - } catch (Exception e) { + } catch (JSONException e) { log.error("Unhandled exception", e); } } @@ -401,21 +405,19 @@ public class DataService extends IntentService { try { if (bundles.containsKey("treatment")) { String trstring = bundles.getString("treatment"); - JSONObject trJson = new JSONObject(trstring); - String _id = trJson.getString("_id"); - handleRemovedRecordFromNS(_id); + JSONObject json = new JSONObject(trstring); + handleTreatmentFromNS(json); } if (bundles.containsKey("treatments")) { String trstring = bundles.getString("treatments"); JSONArray jsonArray = new JSONArray(trstring); for (int i = 0; i < jsonArray.length(); i++) { - JSONObject trJson = jsonArray.getJSONObject(i); - String _id = trJson.getString("_id"); - handleRemovedRecordFromNS(_id); + JSONObject json = jsonArray.getJSONObject(i); + handleTreatmentFromNS(json); } } - } catch (Exception e) { + } catch (JSONException e) { log.error("Unhandled exception", e); } } @@ -474,59 +476,25 @@ public class DataService extends IntentService { } } - if (intent.getAction().equals(Intents.ACTION_NEW_FOOD) || intent.getAction().equals(Intents.ACTION_CHANGED_FOOD)) { - try { - if (bundles.containsKey("food")) { - String trstring = bundles.getString("food"); - handleAddChangeFoodRecord(new JSONObject(trstring)); - } - if (bundles.containsKey("foods")) { - String trstring = bundles.getString("foods"); - JSONArray jsonArray = new JSONArray(trstring); - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject trJson = jsonArray.getJSONObject(i); - handleAddChangeFoodRecord(trJson); - } - } - } catch (Exception e) { - log.error("Unhandled exception", e); - } + if (intent.getAction().equals(Intents.ACTION_NEW_FOOD) + || intent.getAction().equals(Intents.ACTION_CHANGED_FOOD)) { + int mode = Intents.ACTION_NEW_FOOD.equals(intent.getAction()) ? EventNsFood.ADD : EventNsFood.UPDATE; + EventNsFood evt = new EventNsFood(mode, bundles); + MainApp.bus().post(evt); } if (intent.getAction().equals(Intents.ACTION_REMOVED_FOOD)) { - try { - if (bundles.containsKey("food")) { - String trstring = bundles.getString("food"); - JSONObject trJson = new JSONObject(trstring); - String _id = trJson.getString("_id"); - handleRemovedFoodRecord(_id); - } - - if (bundles.containsKey("foods")) { - String trstring = bundles.getString("foods"); - JSONArray jsonArray = new JSONArray(trstring); - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject trJson = jsonArray.getJSONObject(i); - String _id = trJson.getString("_id"); - handleRemovedFoodRecord(_id); - } - } - } catch (Exception e) { - log.error("Unhandled exception", e); - } + EventNsFood evt = new EventNsFood(EventNsFood.REMOVE, bundles); + MainApp.bus().post(evt); } } - private void handleRemovedFoodRecord(String _id) { - MainApp.getDbHelper().foodHelper.deleteFoodById(_id); - } - - public void handleAddChangeFoodRecord(JSONObject trJson) throws JSONException { - MainApp.getDbHelper().foodHelper.createFoodFromJsonIfNotExists(trJson); - } - - private void handleRemovedRecordFromNS(String _id) { - MainApp.getDbHelper().deleteTreatmentById(_id); + private void handleTreatmentFromNS(JSONObject json) { + // new DB model + EventNsTreatment evtTreatment = new EventNsTreatment(EventNsTreatment.REMOVE, json); + MainApp.bus().post(evtTreatment); + // old DB model + String _id = JsonHelper.safeGetString(json, "_id"); MainApp.getDbHelper().deleteTempTargetById(_id); MainApp.getDbHelper().deleteTempBasalById(_id); MainApp.getDbHelper().deleteExtendedBolusById(_id); @@ -534,85 +502,53 @@ public class DataService extends IntentService { MainApp.getDbHelper().deleteProfileSwitchById(_id); } - private void handleAddChangeDataFromNS(String trstring) throws JSONException { - JSONObject trJson = new JSONObject(trstring); - handleDanaRHistoryRecords(trJson); // update record _id in history - handleAddChangeTempTargetRecord(trJson); - handleAddChangeTempBasalRecord(trJson); - handleAddChangeExtendedBolusRecord(trJson); - handleAddChangeCareportalEventRecord(trJson); - handleAddChangeTreatmentRecord(trJson); - handleAddChangeProfileSwitchRecord(trJson); - } - - public void handleDanaRHistoryRecords(JSONObject trJson) { - if (trJson.has(DanaRNSHistorySync.DANARSIGNATURE)) { - MainApp.getDbHelper().updateDanaRHistoryRecordId(trJson); - } - } - - public void handleAddChangeTreatmentRecord(JSONObject trJson) throws JSONException { - if (trJson.has("insulin") || trJson.has("carbs")) { - MainApp.getDbHelper().createTreatmentFromJsonIfNotExists(trJson); - return; - } - } - - public void handleAddChangeTempTargetRecord(JSONObject trJson) throws JSONException { - if (trJson.has("eventType") && trJson.getString("eventType").equals(CareportalEvent.TEMPORARYTARGET)) { - MainApp.getDbHelper().createTemptargetFromJsonIfNotExists(trJson); - } - } - - public void handleAddChangeTempBasalRecord(JSONObject trJson) throws JSONException { - if (trJson.has("eventType") && trJson.getString("eventType").equals(CareportalEvent.TEMPBASAL)) { - MainApp.getDbHelper().createTempBasalFromJsonIfNotExists(trJson); - } - } - - public void handleAddChangeExtendedBolusRecord(JSONObject trJson) throws JSONException { - if (trJson.has("eventType") && trJson.getString("eventType").equals(CareportalEvent.COMBOBOLUS)) { - MainApp.getDbHelper().createExtendedBolusFromJsonIfNotExists(trJson); - } - } - - public void handleAddChangeCareportalEventRecord(JSONObject trJson) throws JSONException { - if (trJson.has("insulin") && trJson.getDouble("insulin") > 0) - return; - if (trJson.has("carbs") && trJson.getDouble("carbs") > 0) - return; - if (trJson.has("eventType") && ( - trJson.getString("eventType").equals(CareportalEvent.SITECHANGE) || - trJson.getString("eventType").equals(CareportalEvent.INSULINCHANGE) || - trJson.getString("eventType").equals(CareportalEvent.SENSORCHANGE) || - trJson.getString("eventType").equals(CareportalEvent.BGCHECK) || - trJson.getString("eventType").equals(CareportalEvent.NOTE) || - trJson.getString("eventType").equals(CareportalEvent.NONE) || - trJson.getString("eventType").equals(CareportalEvent.ANNOUNCEMENT) || - trJson.getString("eventType").equals(CareportalEvent.QUESTION) || - trJson.getString("eventType").equals(CareportalEvent.EXERCISE) || - trJson.getString("eventType").equals(CareportalEvent.OPENAPSOFFLINE) || - trJson.getString("eventType").equals(CareportalEvent.PUMPBATTERYCHANGE) - )) { - MainApp.getDbHelper().createCareportalEventFromJsonIfNotExists(trJson); + private void handleTreatmentFromNS(JSONObject json, Intent intent) throws JSONException { + // new DB model + int mode = Intents.ACTION_NEW_TREATMENT.equals(intent.getAction()) ? EventNsTreatment.ADD : EventNsTreatment.UPDATE; + double insulin = JsonHelper.safeGetDouble(json, "insulin"); + double carbs = JsonHelper.safeGetDouble(json, "carbs"); + String eventType = JsonHelper.safeGetString(json, "eventType"); + if (insulin > 0 || carbs > 0) { + EventNsTreatment evtTreatment = new EventNsTreatment(mode, json); + MainApp.bus().post(evtTreatment); + } else if (json.has(DanaRNSHistorySync.DANARSIGNATURE)) { + // old DB model + MainApp.getDbHelper().updateDanaRHistoryRecordId(json); + } else if (eventType.equals(CareportalEvent.TEMPORARYTARGET)) { + MainApp.getDbHelper().createTemptargetFromJsonIfNotExists(json); + } else if (eventType.equals(CareportalEvent.TEMPBASAL)) { + MainApp.getDbHelper().createTempBasalFromJsonIfNotExists(json); + } else if (eventType.equals(CareportalEvent.COMBOBOLUS)) { + MainApp.getDbHelper().createExtendedBolusFromJsonIfNotExists(json); + } else if (eventType.equals(CareportalEvent.PROFILESWITCH)) { + MainApp.getDbHelper().createProfileSwitchFromJsonIfNotExists(json); + } else if (eventType.equals(CareportalEvent.SITECHANGE) || + eventType.equals(CareportalEvent.INSULINCHANGE) || + eventType.equals(CareportalEvent.SENSORCHANGE) || + eventType.equals(CareportalEvent.BGCHECK) || + eventType.equals(CareportalEvent.NOTE) || + eventType.equals(CareportalEvent.NONE) || + eventType.equals(CareportalEvent.ANNOUNCEMENT) || + eventType.equals(CareportalEvent.QUESTION) || + eventType.equals(CareportalEvent.EXERCISE) || + eventType.equals(CareportalEvent.OPENAPSOFFLINE) || + eventType.equals(CareportalEvent.PUMPBATTERYCHANGE)) { + MainApp.getDbHelper().createCareportalEventFromJsonIfNotExists(json); } - if (trJson.has("eventType") && trJson.getString("eventType").equals(CareportalEvent.ANNOUNCEMENT)) { - long date = trJson.getLong("mills"); + if (eventType.equals(CareportalEvent.ANNOUNCEMENT)) { + long date = JsonHelper.safeGetLong(json,"mills"); long now = System.currentTimeMillis(); - if (date > now - 15 * 60 * 1000L && trJson.has("notes")) { - Notification announcement = new Notification(Notification.NSANNOUNCEMENT, trJson.getString("notes"), Notification.ANNOUNCEMENT, 60); + String enteredBy = JsonHelper.safeGetString(json, "enteredBy", ""); + String notes = JsonHelper.safeGetString(json, "notes", ""); + if (date > now - 15 * 60 * 1000L && !notes.isEmpty() + && !enteredBy.equals(SP.getString("careportal_enteredby", "AndroidAPS"))) { + Notification announcement = new Notification(Notification.NSANNOUNCEMENT, notes, Notification.ANNOUNCEMENT, 60); MainApp.bus().post(new EventNewNotification(announcement)); } } } - public void handleAddChangeProfileSwitchRecord(JSONObject trJson) throws JSONException { - if (trJson.has("eventType") && trJson.getString("eventType").equals(CareportalEvent.PROFILESWITCH)) { - MainApp.getDbHelper().createProfileSwitchFromJsonIfNotExists(trJson); - } - } - private void handleNewSMS(Intent intent) { Bundle bundle = intent.getExtras(); if (bundle == null) return; diff --git a/app/src/main/java/info/nightscout/androidaps/Services/Intents.java b/app/src/main/java/info/nightscout/androidaps/Services/Intents.java index b9c90e905c..744530c8f2 100644 --- a/app/src/main/java/info/nightscout/androidaps/Services/Intents.java +++ b/app/src/main/java/info/nightscout/androidaps/Services/Intents.java @@ -37,6 +37,8 @@ public interface Intents { String EXTRA_SENSOR_BATTERY = "com.eveningoutpost.dexdrip.Extras.SensorBattery"; String EXTRA_TIMESTAMP = "com.eveningoutpost.dexdrip.Extras.Time"; String EXTRA_RAW = "com.eveningoutpost.dexdrip.Extras.Raw"; + String XDRIP_DATA_SOURCE_DESCRIPTION = "com.eveningoutpost.dexdrip.Extras.SourceDesc"; + String ACTION_NEW_BG_ESTIMATE_NO_DATA = "com.eveningoutpost.dexdrip.BgEstimateNoData"; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/activities/DanaRStatsActivity.java b/app/src/main/java/info/nightscout/androidaps/TDDStatsActivity.java similarity index 79% rename from app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/activities/DanaRStatsActivity.java rename to app/src/main/java/info/nightscout/androidaps/TDDStatsActivity.java index 5facf015d8..369b2f42b5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/activities/DanaRStatsActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/TDDStatsActivity.java @@ -1,11 +1,9 @@ -package info.nightscout.androidaps.plugins.PumpDanaR.activities; +package info.nightscout.androidaps; import android.app.Activity; import android.graphics.Color; import android.graphics.Rect; import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; import android.support.v7.widget.LinearLayoutManager; import android.text.TextUtils; import android.view.KeyEvent; @@ -35,21 +33,26 @@ import java.util.Date; import java.util.LinkedList; import java.util.List; -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.DanaRHistoryRecord; +import info.nightscout.androidaps.db.TDD; import info.nightscout.androidaps.events.EventPumpStatusChanged; +import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPlugin; import info.nightscout.androidaps.plugins.PumpDanaR.comm.RecordTypes; import info.nightscout.androidaps.plugins.PumpDanaR.events.EventDanaRSyncStatus; +import info.nightscout.androidaps.plugins.PumpDanaRKorean.DanaRKoreanPlugin; +import info.nightscout.androidaps.plugins.PumpDanaRS.DanaRSPlugin; +import info.nightscout.androidaps.plugins.PumpDanaRv2.DanaRv2Plugin; +import info.nightscout.androidaps.plugins.PumpInsight.InsightPlugin; import info.nightscout.androidaps.queue.Callback; import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.SP; import info.nightscout.utils.SafeParse; -public class DanaRStatsActivity extends Activity { - private static Logger log = LoggerFactory.getLogger(DanaRStatsActivity.class); +public class TDDStatsActivity extends Activity { + private static Logger log = LoggerFactory.getLogger(TDDStatsActivity.class); TextView statusView, statsMessage, totalBaseBasal2; EditText totalBaseBasal; @@ -60,10 +63,10 @@ public class DanaRStatsActivity extends Activity { double magicNumber; DecimalFormat decimalFormat; - List historyList = new ArrayList<>(); - List dummies; + List historyList = new ArrayList<>(); + List dummies; - public DanaRStatsActivity() { + public TDDStatsActivity() { super(); } @@ -126,6 +129,9 @@ public class DanaRStatsActivity extends Activity { } totalBaseBasal.setText(TBB); + if (!ConfigBuilderPlugin.getActivePump().getPumpDescription().needsManualTDDLoad) + reloadButton.setVisibility(View.GONE); + // stats table tl = (TableLayout) findViewById(R.id.main_table); TableRow tr_head = new TableRow(this); @@ -232,10 +238,10 @@ public class DanaRStatsActivity extends Activity { statsMessage.setText(getString(R.string.danar_stats_warning_Message)); } }); - ConfigBuilderPlugin.getCommandQueue().loadHistory(RecordTypes.RECORD_TYPE_DAILY, new Callback() { + ConfigBuilderPlugin.getCommandQueue().loadTDDs( new Callback() { @Override public void run() { - loadDataFromDB(RecordTypes.RECORD_TYPE_DAILY); + loadDataFromDB(); runOnUiThread(new Runnable() { @Override public void run() { @@ -268,18 +274,18 @@ public class DanaRStatsActivity extends Activity { } else { SP.putString("TBB", totalBaseBasal.getText().toString()); TBB = SP.getString("TBB", ""); - loadDataFromDB(RecordTypes.RECORD_TYPE_DAILY); + loadDataFromDB(); InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(totalBaseBasal.getWindowToken(), 0); } } }); - loadDataFromDB(RecordTypes.RECORD_TYPE_DAILY); + loadDataFromDB(); } - private void loadDataFromDB(byte type) { - historyList = MainApp.getDbHelper().getDanaRHistoryRecordsByType(type); + private void loadDataFromDB() { + historyList = MainApp.getDbHelper().getTDDs(); //only use newest 10 historyList = historyList.subList(0, Math.min(10, historyList.size())); @@ -288,24 +294,24 @@ public class DanaRStatsActivity extends Activity { dummies = new LinkedList(); DateFormat df = new SimpleDateFormat("dd.MM."); for (int i = 0; i < historyList.size() - 1; i++) { - DanaRHistoryRecord elem1 = historyList.get(i); - DanaRHistoryRecord elem2 = historyList.get(i + 1); + TDD elem1 = historyList.get(i); + TDD elem2 = historyList.get(i + 1); - if (!df.format(new Date(elem1.recordDate)).equals(df.format(new Date(elem2.recordDate + 25 * 60 * 60 * 1000)))) { - DanaRHistoryRecord dummy = new DanaRHistoryRecord(); - dummy.recordDate = elem1.recordDate - 24 * 60 * 60 * 1000; - dummy.recordDailyBasal = elem1.recordDailyBasal / 2; - dummy.recordDailyBolus = elem1.recordDailyBolus / 2; + if (!df.format(new Date(elem1.date)).equals(df.format(new Date(elem2.date + 25 * 60 * 60 * 1000)))) { + TDD dummy = new TDD(); + dummy.date = elem1.date - 24 * 60 * 60 * 1000; + dummy.basal = elem1.basal / 2; + dummy.bolus = elem1.bolus / 2; dummies.add(dummy); - elem1.recordDailyBasal /= 2; - elem1.recordDailyBolus /= 2; + elem1.basal /= 2; + elem1.bolus /= 2; } } historyList.addAll(dummies); - Collections.sort(historyList, new Comparator() { + Collections.sort(historyList, new Comparator() { @Override - public int compare(DanaRHistoryRecord lhs, DanaRHistoryRecord rhs) { - return (int) (rhs.recordDate - lhs.recordDate); + public int compare(TDD lhs, TDD rhs) { + return (int) (rhs.date - lhs.date); } }); @@ -333,11 +339,13 @@ public class DanaRStatsActivity extends Activity { double weighted05 = 0d; double weighted07 = 0d; - for (DanaRHistoryRecord record : historyList) { - double tdd = record.recordDailyBolus + record.recordDailyBasal; + + //TDD table + for (TDD record : historyList) { + double tdd = record.getTotal(); // Create the table row - TableRow tr = new TableRow(DanaRStatsActivity.this); + TableRow tr = new TableRow(TDDStatsActivity.this); if (i % 2 != 0) tr.setBackgroundColor(Color.DKGRAY); if (dummies.contains(record)) { tr.setBackgroundColor(Color.argb(125, 255, 0, 0)); @@ -348,31 +356,31 @@ public class DanaRStatsActivity extends Activity { TableLayout.LayoutParams.WRAP_CONTENT)); // Here create the TextView dynamically - TextView labelDATE = new TextView(DanaRStatsActivity.this); + TextView labelDATE = new TextView(TDDStatsActivity.this); labelDATE.setId(200 + i); - labelDATE.setText(df.format(new Date(record.recordDate))); + labelDATE.setText(df.format(new Date(record.date))); labelDATE.setTextColor(Color.WHITE); tr.addView(labelDATE); - TextView labelBASAL = new TextView(DanaRStatsActivity.this); + TextView labelBASAL = new TextView(TDDStatsActivity.this); labelBASAL.setId(300 + i); - labelBASAL.setText(DecimalFormatter.to2Decimal(record.recordDailyBasal) + " U"); + labelBASAL.setText(DecimalFormatter.to2Decimal(record.basal) + " U"); labelBASAL.setTextColor(Color.WHITE); tr.addView(labelBASAL); - TextView labelBOLUS = new TextView(DanaRStatsActivity.this); + TextView labelBOLUS = new TextView(TDDStatsActivity.this); labelBOLUS.setId(400 + i); - labelBOLUS.setText(DecimalFormatter.to2Decimal(record.recordDailyBolus) + " U"); + labelBOLUS.setText(DecimalFormatter.to2Decimal(record.bolus) + " U"); labelBOLUS.setTextColor(Color.WHITE); tr.addView(labelBOLUS); - TextView labelTDD = new TextView(DanaRStatsActivity.this); + TextView labelTDD = new TextView(TDDStatsActivity.this); labelTDD.setId(500 + i); labelTDD.setText(DecimalFormatter.to2Decimal(tdd) + " U"); labelTDD.setTextColor(Color.WHITE); tr.addView(labelTDD); - TextView labelRATIO = new TextView(DanaRStatsActivity.this); + TextView labelRATIO = new TextView(TDDStatsActivity.this); labelRATIO.setId(600 + i); labelRATIO.setText(Math.round(100 * tdd / magicNumber) + " %"); labelRATIO.setTextColor(Color.WHITE); @@ -383,11 +391,23 @@ public class DanaRStatsActivity extends Activity { TableLayout.LayoutParams.MATCH_PARENT, TableLayout.LayoutParams.WRAP_CONTENT)); - sum = sum + tdd; + i++; + } + + i = 0; + + //cumulative TDDs + for (TDD record : historyList) { + if(!historyList.isEmpty() && df.format(new Date(record.date)).equals(df.format(new Date()))) { + //Today should not be included + continue; + } i++; + sum = sum + record.getTotal(); + // Create the cumtable row - TableRow ctr = new TableRow(DanaRStatsActivity.this); + TableRow ctr = new TableRow(TDDStatsActivity.this); if (i % 2 == 0) ctr.setBackgroundColor(Color.DKGRAY); ctr.setId(700 + i); ctr.setLayoutParams(new TableLayout.LayoutParams( @@ -395,19 +415,19 @@ public class DanaRStatsActivity extends Activity { TableLayout.LayoutParams.WRAP_CONTENT)); // Here create the TextView dynamically - TextView labelDAYS = new TextView(DanaRStatsActivity.this); + TextView labelDAYS = new TextView(TDDStatsActivity.this); labelDAYS.setId(800 + i); labelDAYS.setText("" + i); labelDAYS.setTextColor(Color.WHITE); ctr.addView(labelDAYS); - TextView labelCUMTDD = new TextView(DanaRStatsActivity.this); + TextView labelCUMTDD = new TextView(TDDStatsActivity.this); labelCUMTDD.setId(900 + i); labelCUMTDD.setText(DecimalFormatter.to2Decimal(sum / i) + " U"); labelCUMTDD.setTextColor(Color.WHITE); ctr.addView(labelCUMTDD); - TextView labelCUMRATIO = new TextView(DanaRStatsActivity.this); + TextView labelCUMRATIO = new TextView(TDDStatsActivity.this); labelCUMRATIO.setId(1000 + i); labelCUMRATIO.setText(Math.round(100 * sum / i / magicNumber) + " %"); labelCUMRATIO.setTextColor(Color.WHITE); @@ -419,7 +439,7 @@ public class DanaRStatsActivity extends Activity { TableLayout.LayoutParams.WRAP_CONTENT)); } - if (historyList.size() < 3 || !(df.format(new Date(historyList.get(0).recordDate)).equals(df.format(new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24))))) { + if (isOldData(historyList) && ConfigBuilderPlugin.getActivePump().getPumpDescription().needsManualTDDLoad) { statsMessage.setVisibility(View.VISIBLE); statsMessage.setText(getString(R.string.danar_stats_olddata_Message)); @@ -427,12 +447,17 @@ public class DanaRStatsActivity extends Activity { tl.setBackgroundColor(Color.TRANSPARENT); } + if(!historyList.isEmpty() && df.format(new Date(historyList.get(0).date)).equals(df.format(new Date()))) { + //Today should not be included + historyList.remove(0); + } + Collections.reverse(historyList); i = 0; - for (DanaRHistoryRecord record : historyList) { - double tdd = record.recordDailyBolus + record.recordDailyBasal; + for (TDD record : historyList) { + double tdd = record.getTotal(); if (i == 0) { weighted03 = tdd; weighted05 = tdd; @@ -447,7 +472,7 @@ public class DanaRStatsActivity extends Activity { } // Create the exptable row - TableRow etr = new TableRow(DanaRStatsActivity.this); + TableRow etr = new TableRow(TDDStatsActivity.this); if (i % 2 != 0) etr.setBackgroundColor(Color.DKGRAY); etr.setId(1100 + i); etr.setLayoutParams(new TableLayout.LayoutParams( @@ -455,13 +480,13 @@ public class DanaRStatsActivity extends Activity { TableLayout.LayoutParams.WRAP_CONTENT)); // Here create the TextView dynamically - TextView labelWEIGHT = new TextView(DanaRStatsActivity.this); + TextView labelWEIGHT = new TextView(TDDStatsActivity.this); labelWEIGHT.setId(1200 + i); labelWEIGHT.setText("0.3\n" + "0.5\n" + "0.7"); labelWEIGHT.setTextColor(Color.WHITE); etr.addView(labelWEIGHT); - TextView labelEXPTDD = new TextView(DanaRStatsActivity.this); + TextView labelEXPTDD = new TextView(TDDStatsActivity.this); labelEXPTDD.setId(1300 + i); labelEXPTDD.setText(DecimalFormatter.to2Decimal(weighted03) + " U\n" + DecimalFormatter.to2Decimal(weighted05) @@ -469,7 +494,7 @@ public class DanaRStatsActivity extends Activity { labelEXPTDD.setTextColor(Color.WHITE); etr.addView(labelEXPTDD); - TextView labelEXPRATIO = new TextView(DanaRStatsActivity.this); + TextView labelEXPRATIO = new TextView(TDDStatsActivity.this); labelEXPRATIO.setId(1400 + i); labelEXPRATIO.setText(Math.round(100 * weighted03 / magicNumber) + " %\n" + Math.round(100 * weighted05 / magicNumber) + " %\n" @@ -516,4 +541,19 @@ public class DanaRStatsActivity extends Activity { } ); } + + + public static boolean isOldData(List historyList) { + Object activePump = MainApp.getConfigBuilder().getActivePump(); + PumpInterface dana = MainApp.getSpecificPlugin(DanaRPlugin.class); + PumpInterface danaRS = MainApp.getSpecificPlugin(DanaRSPlugin.class); + PumpInterface danaV2 = MainApp.getSpecificPlugin(DanaRv2Plugin.class); + PumpInterface danaKorean = MainApp.getSpecificPlugin(DanaRKoreanPlugin.class); + PumpInterface insight = MainApp.getSpecificPlugin(InsightPlugin.class); + + boolean startsYesterday = activePump == dana || activePump == danaRS || activePump == danaV2 || activePump == danaKorean || activePump == insight; + + DateFormat df = new SimpleDateFormat("dd.MM."); + return (historyList.size() < 3 || !(df.format(new Date(historyList.get(0).date)).equals(df.format(new Date(System.currentTimeMillis() - (startsYesterday?1000 * 60 * 60 * 24:0)))))); + } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/data/ConstraintChecker.java b/app/src/main/java/info/nightscout/androidaps/data/ConstraintChecker.java new file mode 100644 index 0000000000..3161cb19f3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/data/ConstraintChecker.java @@ -0,0 +1,198 @@ +package info.nightscout.androidaps.data; + +import java.util.ArrayList; + +import info.nightscout.androidaps.Constants; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.interfaces.BgSourceInterface; +import info.nightscout.androidaps.interfaces.Constraint; +import info.nightscout.androidaps.interfaces.ConstraintsInterface; +import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; + +/** + * Created by mike on 19.03.2018. + */ + +public class ConstraintChecker implements ConstraintsInterface { + + private MainApp mainApp; + + public ConstraintChecker(MainApp mainApp) { + this.mainApp = mainApp; + } + + + public Constraint isLoopInvokationAllowed() { + return isLoopInvokationAllowed(new Constraint<>(true)); + } + + public Constraint isClosedLoopAllowed() { + return isClosedLoopAllowed(new Constraint<>(true)); + } + + public Constraint isAutosensModeEnabled() { + return isAutosensModeEnabled(new Constraint<>(true)); + } + + public Constraint isAMAModeEnabled() { + return isAMAModeEnabled(new Constraint<>(true)); + } + + public Constraint isSMBModeEnabled() { + return isSMBModeEnabled(new Constraint<>(true)); + } + + public Constraint isAdvancedFilteringEnabled() { + return isAdvancedFilteringEnabled(new Constraint<>(true)); + } + + public Constraint getMaxBasalAllowed(Profile profile) { + return applyBasalConstraints(new Constraint<>(Constants.REALLYHIGHBASALRATE), profile); + } + + public Constraint getMaxBasalPercentAllowed(Profile profile) { + return applyBasalPercentConstraints(new Constraint<>(Constants.REALLYHIGHPERCENTBASALRATE), profile); + } + + public Constraint getMaxBolusAllowed() { + return applyBolusConstraints(new Constraint<>(Constants.REALLYHIGHBOLUS)); + } + + public Constraint getMaxCarbsAllowed() { + return applyCarbsConstraints(new Constraint<>(Constants.REALLYHIGHCARBS)); + } + + public Constraint getMaxIOBAllowed() { + return applyMaxIOBConstraints(new Constraint<>(Constants.REALLYHIGHIOB)); + } + + @Override + public Constraint isLoopInvokationAllowed(Constraint value) { + + ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + for (PluginBase p : constraintsPlugins) { + ConstraintsInterface constraint = (ConstraintsInterface) p; + if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; + constraint.isLoopInvokationAllowed(value); + } + return value; + } + + @Override + public Constraint isClosedLoopAllowed(Constraint value) { + + ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + for (PluginBase p : constraintsPlugins) { + ConstraintsInterface constraint = (ConstraintsInterface) p; + if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; + constraint.isClosedLoopAllowed(value); + } + return value; + } + + @Override + public Constraint isAutosensModeEnabled(Constraint value) { + + ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + for (PluginBase p : constraintsPlugins) { + ConstraintsInterface constraint = (ConstraintsInterface) p; + if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; + constraint.isAutosensModeEnabled(value); + } + return value; + } + + @Override + public Constraint isAMAModeEnabled(Constraint value) { + + ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + for (PluginBase p : constraintsPlugins) { + ConstraintsInterface constrain = (ConstraintsInterface) p; + if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; + constrain.isAMAModeEnabled(value); + } + return value; + } + + @Override + public Constraint isSMBModeEnabled(Constraint value) { + + ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + for (PluginBase p : constraintsPlugins) { + ConstraintsInterface constraint = (ConstraintsInterface) p; + if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; + constraint.isSMBModeEnabled(value); + } + return value; + } + + @Override + public Constraint isAdvancedFilteringEnabled(Constraint value) { + ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + for (PluginBase p : constraintsPlugins) { + ConstraintsInterface constraint = (ConstraintsInterface) p; + if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; + constraint.isAdvancedFilteringEnabled(value); + } + return value; + } + + @Override + public Constraint applyBasalConstraints(Constraint absoluteRate, Profile profile) { + ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + for (PluginBase p : constraintsPlugins) { + ConstraintsInterface constraint = (ConstraintsInterface) p; + if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; + constraint.applyBasalConstraints(absoluteRate, profile); + } + return absoluteRate; + } + + @Override + public Constraint applyBasalPercentConstraints(Constraint percentRate, Profile profile) { + ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + for (PluginBase p : constraintsPlugins) { + ConstraintsInterface constrain = (ConstraintsInterface) p; + if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; + constrain.applyBasalPercentConstraints(percentRate, profile); + } + return percentRate; + } + + @Override + public Constraint applyBolusConstraints(Constraint insulin) { + ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + for (PluginBase p : constraintsPlugins) { + ConstraintsInterface constrain = (ConstraintsInterface) p; + if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; + constrain.applyBolusConstraints(insulin); + } + return insulin; + } + + @Override + public Constraint applyCarbsConstraints(Constraint carbs) { + ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + for (PluginBase p : constraintsPlugins) { + ConstraintsInterface constrain = (ConstraintsInterface) p; + if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; + constrain.applyCarbsConstraints(carbs); + } + return carbs; + } + + @Override + public Constraint applyMaxIOBConstraints(Constraint maxIob) { + ArrayList constraintsPlugins = mainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); + for (PluginBase p : constraintsPlugins) { + ConstraintsInterface constrain = (ConstraintsInterface) p; + if (!p.isEnabled(PluginType.CONSTRAINTS)) continue; + constrain.applyMaxIOBConstraints(maxIob); + } + return maxIob; + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/data/DetailedBolusInfo.java b/app/src/main/java/info/nightscout/androidaps/data/DetailedBolusInfo.java index 7720b03d3e..c2f9d16dc3 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/DetailedBolusInfo.java +++ b/app/src/main/java/info/nightscout/androidaps/data/DetailedBolusInfo.java @@ -2,14 +2,14 @@ package info.nightscout.androidaps.data; import android.content.Context; +import com.rits.cloning.Cloner; + import org.json.JSONObject; import java.util.Date; -import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.db.CareportalEvent; import info.nightscout.androidaps.db.Source; -import info.nightscout.androidaps.interfaces.InsulinInterface; /** * Created by mike on 29.05.2017. @@ -29,6 +29,28 @@ public class DetailedBolusInfo { public Context context = null; // context for progress dialog public long pumpId = 0; // id of record if comming from pump history (not a newly created treatment) public boolean isSMB = false; // is a Super-MicroBolus + public long deliverAt = 0; // SMB should be delivered within 1 min from this time + public String notes = null; + + public DetailedBolusInfo copy() { + DetailedBolusInfo n = new DetailedBolusInfo(); + n.date = date; + n.eventType = eventType; + n.insulin = insulin; + n.carbs = carbs; + n.source = source; + n.isValid = isValid; + n.glucose = glucose; + n.glucoseType = glucoseType; + n.carbTime = carbTime; + n.boluscalc = boluscalc; + n.context = context; + n.pumpId = pumpId; + n.isSMB = isSMB; + n.deliverAt = deliverAt; + n.notes = notes; + return n; + } @Override public String toString() { @@ -37,6 +59,7 @@ public class DetailedBolusInfo { " carbs: " + carbs + " isValid: " + isValid + " carbTime: " + carbTime + - " isSMB: " + isSMB; + " isSMB: " + isSMB + + " deliverAt: " + new Date(deliverAt).toLocaleString(); } } diff --git a/app/src/main/java/info/nightscout/androidaps/data/GlucoseStatus.java b/app/src/main/java/info/nightscout/androidaps/data/GlucoseStatus.java index ad4f161799..d8ee55b73a 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/GlucoseStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/data/GlucoseStatus.java @@ -21,6 +21,7 @@ import java.util.List; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.db.BgReading; +import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.Round; @@ -35,21 +36,15 @@ public class GlucoseStatus { public double avgdelta = 0d; public double short_avgdelta = 0d; public double long_avgdelta = 0d; + public long date = 0L; @Override public String toString() { - return MainApp.sResources.getString(R.string.glucose) + " " + DecimalFormatter.to0Decimal(glucose) + " mg/dl\n" + - MainApp.sResources.getString(R.string.delta) + " " + DecimalFormatter.to0Decimal(delta) + " mg/dl\n" + - MainApp.sResources.getString(R.string.short_avgdelta) + " " + DecimalFormatter.to2Decimal(short_avgdelta) + " mg/dl\n" + - MainApp.sResources.getString(R.string.long_avgdelta) + " " + DecimalFormatter.to2Decimal(long_avgdelta) + " mg/dl"; - } - - public Spanned toSpanned() { - return Html.fromHtml("" + MainApp.sResources.getString(R.string.glucose) + ": " + DecimalFormatter.to0Decimal(glucose) + " mg/dl
" + - "" + MainApp.sResources.getString(R.string.delta) + ": " + DecimalFormatter.to0Decimal(delta) + " mg/dl
" + - "" + MainApp.sResources.getString(R.string.short_avgdelta) + ": " + DecimalFormatter.to2Decimal(short_avgdelta) + " mg/dl
" + - "" + MainApp.sResources.getString(R.string.long_avgdelta) + ": " + DecimalFormatter.to2Decimal(long_avgdelta) + " mg/dl"); + return MainApp.gs(R.string.glucose) + " " + DecimalFormatter.to0Decimal(glucose) + " mg/dl\n" + + MainApp.gs(R.string.delta) + " " + DecimalFormatter.to0Decimal(delta) + " mg/dl\n" + + MainApp.gs(R.string.short_avgdelta) + " " + DecimalFormatter.to2Decimal(short_avgdelta) + " mg/dl\n" + + MainApp.gs(R.string.long_avgdelta) + " " + DecimalFormatter.to2Decimal(long_avgdelta) + " mg/dl"; } public GlucoseStatus() { @@ -74,11 +69,15 @@ public class GlucoseStatus { @Nullable public static GlucoseStatus getGlucoseStatusData(boolean allowOldData) { // load 45min - long fromtime = (long) (System.currentTimeMillis() - 60 * 1000L * 45); + long fromtime = DateUtil.now() - 60 * 1000L * 45; List data = MainApp.getDbHelper().getBgreadingsDataFromTime(fromtime, false); int sizeRecords = data.size(); - if (sizeRecords < 1 || (data.get(0).date < System.currentTimeMillis() - 7 * 60 * 1000L && !allowOldData)) { + if (sizeRecords == 0) { + return null; + } + + if (data.get(0).date < DateUtil.now() - 7 * 60 * 1000L && !allowOldData) { return null; } @@ -86,13 +85,14 @@ public class GlucoseStatus { long now_date = now.date; double change; - if (sizeRecords < 2) { + if (sizeRecords == 1) { GlucoseStatus status = new GlucoseStatus(); status.glucose = now.value; status.short_avgdelta = 0d; status.delta = 0d; status.long_avgdelta = 0d; status.avgdelta = 0d; // for OpenAPS MA + status.date = now_date; return status.round(); } @@ -133,6 +133,7 @@ public class GlucoseStatus { GlucoseStatus status = new GlucoseStatus(); status.glucose = now.value; + status.date = now_date; status.short_avgdelta = average(short_deltas); diff --git a/app/src/main/java/info/nightscout/androidaps/data/Intervals.java b/app/src/main/java/info/nightscout/androidaps/data/Intervals.java index 108b4060e0..66d567d9bd 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/Intervals.java +++ b/app/src/main/java/info/nightscout/androidaps/data/Intervals.java @@ -16,7 +16,11 @@ import info.nightscout.androidaps.interfaces.Interval; public abstract class Intervals { - LongSparseArray rawData = new LongSparseArray(); // oldest at index 0 + LongSparseArray rawData; // oldest at index 0 + + public Intervals() { + rawData = new LongSparseArray(); + } public synchronized Intervals reset() { rawData = new LongSparseArray(); @@ -27,8 +31,7 @@ public abstract class Intervals { /** * The List must be sorted by `T.start()` in ascending order - * - * */ + */ public synchronized void add(List list) { for (T interval : list) { rawData.put(interval.start(), interval); @@ -36,6 +39,10 @@ public abstract class Intervals { merge(); } + public synchronized void add(T interval) { + rawData.put(interval.start(), interval); + merge(); + } public synchronized List getList() { @@ -47,7 +54,7 @@ public abstract class Intervals { public synchronized List getReversedList() { List list = new ArrayList<>(); - for (int i = rawData.size() -1; i>=0; i--) + for (int i = rawData.size() - 1; i >= 0; i--) list.add(rawData.valueAt(i)); return list; } @@ -86,5 +93,4 @@ public abstract class Intervals { } - } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/data/Iob.java b/app/src/main/java/info/nightscout/androidaps/data/Iob.java index 762352782b..d48cdf627c 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/Iob.java +++ b/app/src/main/java/info/nightscout/androidaps/data/Iob.java @@ -7,6 +7,16 @@ public class Iob { public double iobContrib = 0d; public double activityContrib = 0d; + public Iob iobContrib(double iobContrib) { + this.iobContrib = iobContrib; + return this; + } + + public Iob activityContrib(double activityContrib) { + this.activityContrib = activityContrib; + return this; + } + public Iob plus(Iob iob) { iobContrib += iob.iobContrib; activityContrib += iob.activityContrib; diff --git a/app/src/main/java/info/nightscout/androidaps/data/IobTotal.java b/app/src/main/java/info/nightscout/androidaps/data/IobTotal.java index 3240e79327..a87b77be07 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/IobTotal.java +++ b/app/src/main/java/info/nightscout/androidaps/data/IobTotal.java @@ -1,5 +1,7 @@ package info.nightscout.androidaps.data; +import com.rits.cloning.Cloner; + import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; @@ -21,8 +23,8 @@ public class IobTotal { public double hightempinsulin; // oref1 - public double microBolusInsulin; - public double microBolusIOB; + public long lastBolusTime; + public IobTotal iobWithZeroTemp; public double netInsulin = 0d; // for calculations from temp basals only public double netRatio = 0d; // net ratio at start of temp basal @@ -31,6 +33,12 @@ public class IobTotal { long time; + + public IobTotal copy() { + Cloner cloner = new Cloner(); + return cloner.deepClone(this); + } + public IobTotal(long time) { this.iob = 0d; this.activity = 0d; @@ -38,8 +46,7 @@ public class IobTotal { this.basaliob = 0d; this.netbasalinsulin = 0d; this.hightempinsulin = 0d; - this.microBolusInsulin = 0d; - this.microBolusIOB = 0d; + this.lastBolusTime = 0; this.time = time; } @@ -52,8 +59,6 @@ public class IobTotal { hightempinsulin += other.hightempinsulin; netInsulin += other.netInsulin; extendedBolusInsulin += other.extendedBolusInsulin; - microBolusInsulin += other.microBolusInsulin; - microBolusIOB += other.microBolusIOB; return this; } @@ -62,11 +67,13 @@ public class IobTotal { result.iob = bolusIOB.iob + basalIob.basaliob; result.activity = bolusIOB.activity + basalIob.activity; result.bolussnooze = bolusIOB.bolussnooze; - result.basaliob = basalIob.basaliob; - result.netbasalinsulin = basalIob.netbasalinsulin; - result.hightempinsulin = basalIob.hightempinsulin; - result.microBolusInsulin = bolusIOB.microBolusInsulin + basalIob.microBolusInsulin; - result.microBolusIOB = bolusIOB.microBolusIOB + basalIob.microBolusIOB; + result.basaliob = bolusIOB.basaliob + basalIob.basaliob; + result.netbasalinsulin = bolusIOB.netbasalinsulin + basalIob.netbasalinsulin; + result.hightempinsulin = basalIob.hightempinsulin + bolusIOB.hightempinsulin; + result.netInsulin = basalIob.netInsulin + bolusIOB.netInsulin; + result.extendedBolusInsulin = basalIob.extendedBolusInsulin + bolusIOB.extendedBolusInsulin; + result.lastBolusTime = bolusIOB.lastBolusTime; + result.iobWithZeroTemp = basalIob.iobWithZeroTemp; return result; } @@ -77,8 +84,8 @@ public class IobTotal { this.basaliob = Round.roundTo(this.basaliob, 0.001); this.netbasalinsulin = Round.roundTo(this.netbasalinsulin, 0.001); this.hightempinsulin = Round.roundTo(this.hightempinsulin, 0.001); - this.microBolusInsulin = Round.roundTo(this.microBolusInsulin, 0.001); - this.microBolusIOB = Round.roundTo(this.microBolusIOB, 0.001); + this.netInsulin = Round.roundTo(this.netInsulin, 0.001); + this.extendedBolusInsulin = Round.roundTo(this.extendedBolusInsulin, 0.001); return this; } @@ -102,7 +109,24 @@ public class IobTotal { json.put("basaliob", basaliob); json.put("bolussnooze", bolussnooze); json.put("activity", activity); + json.put("lastBolusTime", lastBolusTime); json.put("time", DateUtil.toISOString(new Date(time))); + /* + + This is requested by SMB determine_basal but by based on Scott's info + it's MDT specific safety check only + It's causing rounding issues in determine_basal + + JSONObject lastTemp = new JSONObject(); + lastTemp.put("date", lastTempDate); + lastTemp.put("rate", lastTempRate); + lastTemp.put("duration", lastTempDuration); + json.put("lastTemp", lastTemp); + */ + if (iobWithZeroTemp != null) { + JSONObject iwzt = iobWithZeroTemp.determineBasalJson(); + json.put("iobWithZeroTemp", iwzt); + } } catch (JSONException e) { log.error("Unhandled exception", e); } diff --git a/app/src/main/java/info/nightscout/androidaps/data/MealData.java b/app/src/main/java/info/nightscout/androidaps/data/MealData.java index 292d2272d5..8acf4285bf 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/MealData.java +++ b/app/src/main/java/info/nightscout/androidaps/data/MealData.java @@ -7,4 +7,9 @@ public class MealData { public double boluses = 0d; public double carbs = 0d; public double mealCOB = 0.0d; + public double slopeFromMaxDeviation = 0; + public double slopeFromMinDeviation = 999; + public long lastBolusTime; + public long lastCarbTime = 0L; + public double usedMinCarbsImpact = 0d; } diff --git a/app/src/main/java/info/nightscout/androidaps/data/NonOverlappingIntervals.java b/app/src/main/java/info/nightscout/androidaps/data/NonOverlappingIntervals.java index 385eeb9594..8e0c286e7e 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/NonOverlappingIntervals.java +++ b/app/src/main/java/info/nightscout/androidaps/data/NonOverlappingIntervals.java @@ -2,7 +2,9 @@ package info.nightscout.androidaps.data; import android.support.annotation.Nullable; +import android.support.v4.util.LongSparseArray; +import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.interfaces.Interval; /** @@ -11,6 +13,14 @@ import info.nightscout.androidaps.interfaces.Interval; public class NonOverlappingIntervals extends Intervals { + public NonOverlappingIntervals() { + super(); + } + + public NonOverlappingIntervals (Intervals other) { + rawData = other.rawData.clone(); + } + protected synchronized void merge() { for (int index = 0; index < rawData.size() - 1; index++) { Interval i = rawData.valueAt(index); @@ -27,4 +37,5 @@ public class NonOverlappingIntervals extends Intervals { if (index >= 0) return rawData.valueAt(index); return null; } + } diff --git a/app/src/main/java/info/nightscout/androidaps/data/OverlappingIntervals.java b/app/src/main/java/info/nightscout/androidaps/data/OverlappingIntervals.java index 070426cca0..76c2ff3615 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/OverlappingIntervals.java +++ b/app/src/main/java/info/nightscout/androidaps/data/OverlappingIntervals.java @@ -11,18 +11,26 @@ import info.nightscout.androidaps.interfaces.Interval; public class OverlappingIntervals extends Intervals { + public OverlappingIntervals() { + super(); + } + + public OverlappingIntervals(Intervals other) { + rawData = other.rawData.clone(); + } + protected synchronized void merge() { boolean needToCut = false; long cutTime = 0; - for (int index = rawData.size()-1; index >= 0; index--) { //begin with newest + for (int index = rawData.size() - 1; index >= 0; index--) { //begin with newest Interval cur = rawData.valueAt(index); - if (cur.isEndingEvent()){ + if (cur.isEndingEvent()) { needToCut = true; cutTime = cur.start(); } else { //event that is no EndingEvent might need to be stopped by an ending event - if(needToCut&&cur.end() > cutTime){ + if (needToCut && cur.end() > cutTime) { cur.cutEndTo(cutTime); } } @@ -31,9 +39,9 @@ public class OverlappingIntervals extends Intervals { @Nullable public synchronized T getValueByInterval(long time) { - for (int index = rawData.size()-1; index >= 0; index--) { //begin with newest + for (int index = rawData.size() - 1; index >= 0; index--) { //begin with newest T cur = rawData.valueAt(index); - if (cur.match(time)){ + if (cur.match(time)) { return cur; } } diff --git a/app/src/main/java/info/nightscout/androidaps/data/Profile.java b/app/src/main/java/info/nightscout/androidaps/data/Profile.java index 7bb693ef23..783124168f 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/Profile.java +++ b/app/src/main/java/info/nightscout/androidaps/data/Profile.java @@ -2,8 +2,6 @@ package info.nightscout.androidaps.data; import android.support.v4.util.LongSparseArray; -import com.crashlytics.android.Crashlytics; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -14,56 +12,76 @@ import java.text.DecimalFormat; import java.util.Calendar; import java.util.TimeZone; +import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.interfaces.PumpDescription; import info.nightscout.androidaps.interfaces.PumpInterface; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; -import info.nightscout.utils.ToastUtils; +import info.nightscout.utils.FabricPrivacy; public class Profile { private static Logger log = LoggerFactory.getLogger(Profile.class); private JSONObject json; - private String units = null; - private double dia = Constants.defaultDIA; - private TimeZone timeZone = TimeZone.getDefault(); + private String units; + private double dia; + private TimeZone timeZone; private JSONArray isf; - private LongSparseArray isf_v = null; // oldest at index 0 + private LongSparseArray isf_v; // oldest at index 0 private JSONArray ic; - private LongSparseArray ic_v = null; // oldest at index 0 + private LongSparseArray ic_v; // oldest at index 0 private JSONArray basal; - private LongSparseArray basal_v = null; // oldest at index 0 + private LongSparseArray basal_v; // oldest at index 0 private JSONArray targetLow; - private LongSparseArray targetLow_v = null; // oldest at index 0 + private LongSparseArray targetLow_v; // oldest at index 0 private JSONArray targetHigh; - private LongSparseArray targetHigh_v = null; // oldest at index 0 + private LongSparseArray targetHigh_v; // oldest at index 0 - private int percentage = 100; - private int timeshift = 0; + private int percentage; + private int timeshift; - private boolean isValid = true; - private boolean isValidated = false; + protected boolean isValid; + protected boolean isValidated; + // Default constructor for tests + protected Profile() { + } + + // Constructor from profileStore JSON public Profile(JSONObject json, String units) { - this(json, 100, 0); + init(json, 100, 0); if (this.units == null) { if (units != null) this.units = units; else { - Crashlytics.log("Profile failover failed too"); + FabricPrivacy.log("Profile failover failed too"); this.units = Constants.MGDL; } } } public Profile(JSONObject json, int percentage, int timeshift) { + init(json, percentage, timeshift); + } + + protected void init(JSONObject json, int percentage, int timeshift) { + units = null; + dia = Constants.defaultDIA; + timeZone = TimeZone.getDefault(); + isf_v = null; + ic_v = null; + basal_v = null; + targetLow_v = null; + targetHigh_v = null; + + isValid = true; + isValidated = false; + this.percentage = percentage; this.timeshift = timeshift; this.json = json; @@ -77,53 +95,12 @@ public class Profile { if (json.has("timezone")) timeZone = TimeZone.getTimeZone(json.getString("timezone")); isf = json.getJSONArray("sens"); - if (getIsf(0) == null) { - int defaultISF = units.equals(Constants.MGDL) ? 400 : 20; - isf = new JSONArray("[{\"time\":\"00:00\",\"value\":\"" + defaultISF + "\",\"timeAsSeconds\":\"0\"}]"); - Notification noisf = new Notification(Notification.ISF_MISSING, MainApp.sResources.getString(R.string.isfmissing), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(noisf)); - } else { - MainApp.bus().post(new EventDismissNotification(Notification.ISF_MISSING)); - } ic = json.getJSONArray("carbratio"); - if (getIc(0) == null) { - int defaultIC = 25; - ic = new JSONArray("[{\"time\":\"00:00\",\"value\":\"" + defaultIC + "\",\"timeAsSeconds\":\"0\"}]"); - Notification noic = new Notification(Notification.IC_MISSING, MainApp.sResources.getString(R.string.icmissing), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(noic)); - } else { - MainApp.bus().post(new EventDismissNotification(Notification.IC_MISSING)); - } basal = json.getJSONArray("basal"); - if (getBasal(0) == null) { - double defaultBasal = 0.1d; - basal = new JSONArray("[{\"time\":\"00:00\",\"value\":\"" + defaultBasal + "\",\"timeAsSeconds\":\"0\"}]"); - Notification nobasal = new Notification(Notification.BASAL_MISSING, MainApp.sResources.getString(R.string.basalmissing), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(nobasal)); - } else { - MainApp.bus().post(new EventDismissNotification(Notification.BASAL_MISSING)); - } targetLow = json.getJSONArray("target_low"); - if (getTargetLow(0) == null) { - double defaultLow = units.equals(Constants.MGDL) ? 120 : 6; - targetLow = new JSONArray("[{\"time\":\"00:00\",\"value\":\"" + defaultLow + "\",\"timeAsSeconds\":\"0\"}]"); - Notification notarget = new Notification(Notification.TARGET_MISSING, MainApp.sResources.getString(R.string.targetmissing), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notarget)); - } else { - MainApp.bus().post(new EventDismissNotification(Notification.TARGET_MISSING)); - } targetHigh = json.getJSONArray("target_high"); - if (getTargetHigh(0) == null) { - double defaultHigh = units.equals(Constants.MGDL) ? 160 : 8; - targetHigh = new JSONArray("[{\"time\":\"00:00\",\"value\":\"" + defaultHigh + "\",\"timeAsSeconds\":\"0\"}]"); - Notification notarget = new Notification(Notification.TARGET_MISSING, MainApp.sResources.getString(R.string.targetmissing), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notarget)); - } else { - MainApp.bus().post(new EventDismissNotification(Notification.TARGET_MISSING)); - } } catch (JSONException e) { log.error("Unhandled exception", e); - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.invalidprofile)); isValid = false; isValidated = true; } @@ -132,7 +109,7 @@ public class Profile { public String log() { String ret = "\n"; for (Integer hour = 0; hour < 24; hour++) { - double value = getBasal((Integer) (hour * 60 * 60)); + double value = getBasalTimeFromMidnight((Integer) (hour * 60 * 60)); ret += "NS basal value for " + hour + ":00 is " + value + "\n"; } ret += "NS units: " + getUnits(); @@ -154,6 +131,10 @@ public class Profile { } // mmol or mg/dl + public void setUnits(String units) { + this.units = units; + } + public String getUnits() { return units; } @@ -163,6 +144,11 @@ public class Profile { } private LongSparseArray convertToSparseArray(JSONArray array) { + if (array == null) { + isValid = false; + return new LongSparseArray<>(); + } + double multiplier = getMultiplier(array); LongSparseArray sparse = new LongSparseArray<>(); @@ -217,22 +203,24 @@ public class Profile { if (isValid) { // Check for hours alignment - for (int index = 0; index < basal_v.size(); index++) { - long secondsFromMidnight = basal_v.keyAt(index); - if (secondsFromMidnight % 3600 != 0) { - Notification notification = new Notification(Notification.BASAL_PROFILE_NOT_ALIGNED_TO_HOURS, String.format(MainApp.gs(R.string.basalprofilenotaligned), from), Notification.NORMAL); - MainApp.bus().post(new EventNewNotification(notification)); + PumpInterface pump = MainApp.getConfigBuilder().getActivePump(); + if (pump != null && !pump.getPumpDescription().is30minBasalRatesCapable) { + for (int index = 0; index < basal_v.size(); index++) { + long secondsFromMidnight = basal_v.keyAt(index); + if (secondsFromMidnight % 3600 != 0) { + Notification notification = new Notification(Notification.BASAL_PROFILE_NOT_ALIGNED_TO_HOURS, String.format(MainApp.gs(R.string.basalprofilenotaligned), from), Notification.NORMAL); + MainApp.bus().post(new EventNewNotification(notification)); + } } } // Check for minimal basal value - PumpInterface pump = ConfigBuilderPlugin.getActivePump(); if (pump != null) { PumpDescription description = pump.getPumpDescription(); for (int i = 0; i < basal_v.size(); i++) { if (basal_v.valueAt(i) < description.basalMinimumRate) { basal_v.setValueAt(i, description.basalMinimumRate); - MainApp.bus().post(new EventNewNotification(new Notification(Notification.MINIMAL_BASAL_VALUE_REPLACED, String.format(MainApp.gs(R.string.minimalbasalvaluereplaced), from), Notification.NORMAL))); + sendBelowMinimumNotification(from); } } } else { @@ -246,6 +234,10 @@ public class Profile { return isValid; } + protected void sendBelowMinimumNotification(String from) { + MainApp.bus().post(new EventNewNotification(new Notification(Notification.MINIMAL_BASAL_VALUE_REPLACED, String.format(MainApp.gs(R.string.minimalbasalvaluereplaced), from), Notification.NORMAL))); + } + private void validate(LongSparseArray array) { if (array.size() == 0) { isValid = false; @@ -259,6 +251,7 @@ public class Profile { } } + /* private Double getValueToTime(JSONArray array, Integer timeAsSeconds) { Double lastValue = null; @@ -278,11 +271,12 @@ public class Profile { } return lastValue; } + */ Integer getShitfTimeSecs(Integer originalTime) { Integer shiftedTime = originalTime + timeshift * 60 * 60; shiftedTime = (shiftedTime + 24 * 60 * 60) % (24 * 60 * 60); - if (timeshift != 0) + if (timeshift != 0 && Config.logProfile) log.debug("(Sec) Original time: " + originalTime + " ShiftedTime: " + shiftedTime); return shiftedTime; } @@ -319,7 +313,7 @@ public class Profile { return multiplier; } - private Double getValueToTime(LongSparseArray array, Integer timeAsSeconds) { + private double getValueToTime(LongSparseArray array, Integer timeAsSeconds) { Double lastValue = null; for (Integer index = 0; index < array.size(); index++) { @@ -334,7 +328,7 @@ public class Profile { return lastValue; } - private String format_HH_MM(Integer timeAsSeconds) { + protected String format_HH_MM(Integer timeAsSeconds) { String time; int hour = timeAsSeconds / 60 / 60; int minutes = (timeAsSeconds - hour * 60 * 60) / 60; @@ -361,51 +355,55 @@ public class Profile { return retValue; } - public Double getIsf() { - return getIsf(secondsFromMidnight(System.currentTimeMillis())); + public double getIsf() { + return getIsfTimeFromMidnight(secondsFromMidnight(System.currentTimeMillis())); } - public Double getIsf(long time) { - return getIsf(secondsFromMidnight(time)); + public double getIsf(long time) { + return getIsfTimeFromMidnight(secondsFromMidnight(time)); } - public Double getIsf(Integer timeAsSeconds) { + double getIsfTimeFromMidnight(int timeAsSeconds) { if (isf_v == null) isf_v = convertToSparseArray(isf); return getValueToTime(isf_v, timeAsSeconds); } public String getIsfList() { + if (isf_v == null) + isf_v = convertToSparseArray(isf); return getValuesList(isf_v, null, new DecimalFormat("0.0"), getUnits() + "/U"); } - public Double getIc() { - return getIc(secondsFromMidnight(System.currentTimeMillis())); + public double getIc() { + return getIcTimeFromMidnight(secondsFromMidnight(System.currentTimeMillis())); } - public Double getIc(long time) { - return getIc(secondsFromMidnight(time)); + public double getIc(long time) { + return getIcTimeFromMidnight(secondsFromMidnight(time)); } - public Double getIc(Integer timeAsSeconds) { + public double getIcTimeFromMidnight(int timeAsSeconds) { if (ic_v == null) ic_v = convertToSparseArray(ic); return getValueToTime(ic_v, timeAsSeconds); } public String getIcList() { - return getValuesList(ic_v, null, new DecimalFormat("0.0"), " g/U"); + if (ic_v == null) + ic_v = convertToSparseArray(ic); + return getValuesList(ic_v, null, new DecimalFormat("0.0"), "g/U"); } - public Double getBasal() { - return getBasal(secondsFromMidnight(System.currentTimeMillis())); + public double getBasal() { + return getBasalTimeFromMidnight(secondsFromMidnight(System.currentTimeMillis())); } - public Double getBasal(long time) { - return getBasal(secondsFromMidnight(time)); + public double getBasal(long time) { + return getBasalTimeFromMidnight(secondsFromMidnight(time)); } - public synchronized Double getBasal(Integer timeAsSeconds) { + public synchronized double getBasalTimeFromMidnight(int timeAsSeconds) { if (basal_v == null) { basal_v = convertToSparseArray(basal); } @@ -413,17 +411,19 @@ public class Profile { } public String getBasalList() { - return getValuesList(basal_v, null, new DecimalFormat("0.00"), "U"); + if (basal_v == null) + basal_v = convertToSparseArray(basal); + return getValuesList(basal_v, null, new DecimalFormat("0.00"), "U/h"); } public class BasalValue { - public BasalValue(Integer timeAsSeconds, Double value) { + public BasalValue(int timeAsSeconds, double value) { this.timeAsSeconds = timeAsSeconds; this.value = value; } - public Integer timeAsSeconds; - public Double value; + public int timeAsSeconds; + public double value; } public synchronized BasalValue[] getBasalValues() { @@ -433,54 +433,66 @@ public class Profile { for (Integer index = 0; index < basal_v.size(); index++) { Integer tas = (int) basal_v.keyAt(index); - Double value = basal_v.valueAt(index); + double value = basal_v.valueAt(index); ret[index] = new BasalValue(tas, value); } return ret; } - public Double getTargetLow() { - return getTargetLow(secondsFromMidnight(System.currentTimeMillis())); + public double getTarget(){ + return getTarget(secondsFromMidnight(System.currentTimeMillis())); } - public Double getTargetLow(long time) { - return getTargetLow(secondsFromMidnight(time)); + protected double getTarget(int timeAsSeconds) { + return (getTargetLowTimeFromMidnight(timeAsSeconds) + getTargetHighTimeFromMidnight(timeAsSeconds))/2; } - public Double getTargetLow(Integer timeAsSeconds) { + public double getTargetLow() { + return getTargetLowTimeFromMidnight(secondsFromMidnight(System.currentTimeMillis())); + } + + public double getTargetLow(long time) { + return getTargetLowTimeFromMidnight(secondsFromMidnight(time)); + } + + public double getTargetLowTimeFromMidnight(int timeAsSeconds) { if (targetLow_v == null) targetLow_v = convertToSparseArray(targetLow); return getValueToTime(targetLow_v, timeAsSeconds); } - public Double getTargetHigh() { - return getTargetHigh(secondsFromMidnight(System.currentTimeMillis())); + public double getTargetHigh() { + return getTargetHighTimeFromMidnight(secondsFromMidnight(System.currentTimeMillis())); } - public Double getTargetHigh(long time) { - return getTargetHigh(secondsFromMidnight(time)); + public double getTargetHigh(long time) { + return getTargetHighTimeFromMidnight(secondsFromMidnight(time)); } - public Double getTargetHigh(Integer timeAsSeconds) { + public double getTargetHighTimeFromMidnight(int timeAsSeconds) { if (targetHigh_v == null) targetHigh_v = convertToSparseArray(targetHigh); return getValueToTime(targetHigh_v, timeAsSeconds); } public String getTargetList() { + if (targetLow_v == null) + targetLow_v = convertToSparseArray(targetLow); + if (targetHigh_v == null) + targetHigh_v = convertToSparseArray(targetHigh); return getValuesList(targetLow_v, targetHigh_v, new DecimalFormat("0.0"), getUnits()); } public double getMaxDailyBasal() { - Double max = 0d; - for (Integer hour = 0; hour < 24; hour++) { - double value = getBasal((Integer) (hour * 60 * 60)); + double max = 0d; + for (int hour = 0; hour < 24; hour++) { + double value = getBasalTimeFromMidnight((Integer) (hour * 60 * 60)); if (value > max) max = value; } return max; } - public static Integer secondsFromMidnight() { + public static int secondsFromMidnight() { Calendar c = Calendar.getInstance(); long now = c.getTimeInMillis(); c.set(Calendar.HOUR_OF_DAY, 0); @@ -491,7 +503,7 @@ public class Profile { return (int) (passed / 1000); } - public static Integer secondsFromMidnight(long date) { + public static int secondsFromMidnight(long date) { Calendar c = Calendar.getInstance(); c.setTimeInMillis(date); c.set(Calendar.HOUR_OF_DAY, 0); @@ -502,22 +514,22 @@ public class Profile { return (int) (passed / 1000); } - public static Double toMgdl(Double value, String units) { + public static double toMgdl(double value, String units) { if (units.equals(Constants.MGDL)) return value; else return value * Constants.MMOLL_TO_MGDL; } - public static Double toMmol(Double value, String units) { + public static double toMmol(double value, String units) { if (units.equals(Constants.MGDL)) return value * Constants.MGDL_TO_MMOLL; else return value; } - public static Double fromMgdlToUnits(Double value, String units) { + public static double fromMgdlToUnits(double value, String units) { if (units.equals(Constants.MGDL)) return value; else return value * Constants.MGDL_TO_MMOLL; } - public static Double toUnits(Double valueInMgdl, Double valueInMmol, String units) { + public static double toUnits(Double valueInMgdl, Double valueInMmol, String units) { if (units.equals(Constants.MGDL)) return valueInMgdl; else return valueInMmol; } @@ -543,7 +555,7 @@ public class Profile { public double percentageBasalSum() { double result = 0d; for (int i = 0; i < 24; i++) { - result += getBasal((Integer) (i * 60 * 60)); + result += getBasalTimeFromMidnight(i * 60 * 60); } return result; } @@ -552,7 +564,7 @@ public class Profile { public double baseBasalSum() { double result = 0d; for (int i = 0; i < 24; i++) { - result += getBasal((Integer) (i * 60 * 60)) / getMultiplier(basal_v); + result += getBasalTimeFromMidnight(i * 60 * 60) / getMultiplier(basal_v); } return result; } diff --git a/app/src/main/java/info/nightscout/androidaps/data/ProfileIntervals.java b/app/src/main/java/info/nightscout/androidaps/data/ProfileIntervals.java index 0ea76ffb15..9343596eee 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/ProfileIntervals.java +++ b/app/src/main/java/info/nightscout/androidaps/data/ProfileIntervals.java @@ -3,10 +3,14 @@ package info.nightscout.androidaps.data; import android.support.annotation.Nullable; import android.support.v4.util.LongSparseArray; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.util.ArrayList; import java.util.List; import info.nightscout.androidaps.interfaces.Interval; +import info.nightscout.utils.DateUtil; /** * Created by mike on 09.05.2017. @@ -16,8 +20,17 @@ import info.nightscout.androidaps.interfaces.Interval; // When no interval match the lastest record without duration is used public class ProfileIntervals { + private static Logger log = LoggerFactory.getLogger(ProfileIntervals.class); - private LongSparseArray rawData = new LongSparseArray<>(); // oldest at index 0 + private LongSparseArray rawData; // oldest at index 0 + + public ProfileIntervals () { + rawData = new LongSparseArray<>(); + } + + public ProfileIntervals (ProfileIntervals other) { + rawData = other.rawData.clone(); + } public synchronized ProfileIntervals reset() { rawData = new LongSparseArray<>(); @@ -25,8 +38,10 @@ public class ProfileIntervals { } public synchronized void add(T newInterval) { - rawData.put(newInterval.start(), newInterval); - merge(); + if (newInterval.isValid()) { + rawData.put(newInterval.start(), newInterval); + merge(); + } } public synchronized void add(List list) { @@ -51,6 +66,13 @@ public class ProfileIntervals { public synchronized Interval getValueToTime(long time) { int index = binarySearch(time); if (index >= 0) return rawData.valueAt(index); + // if we request data older than first record, use oldest with zero duration instead + for (index = 0; index < rawData.size(); index++) { + if (rawData.valueAt(index).durationInMsec() == 0) { + //log.debug("Requested profile for time: " + DateUtil.dateAndTimeString(time) + ". Providing oldest record: " + rawData.valueAt(0).toString()); + return rawData.valueAt(index); + } + } return null; } @@ -107,4 +129,9 @@ public class ProfileIntervals { public synchronized T getReversed(int index) { return rawData.valueAt(size() - 1 - index); } + + @Override + public String toString() { + return rawData.toString(); + } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/data/PumpEnactResult.java b/app/src/main/java/info/nightscout/androidaps/data/PumpEnactResult.java index c50166d5a4..eebd42f5d2 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/PumpEnactResult.java +++ b/app/src/main/java/info/nightscout/androidaps/data/PumpEnactResult.java @@ -13,7 +13,7 @@ import info.nightscout.androidaps.R; import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.Round; -public class PumpEnactResult extends Object { +public class PumpEnactResult { private static Logger log = LoggerFactory.getLogger(PumpEnactResult.class); public boolean success = false; // request was processed successfully (but possible no change was needed) @@ -21,19 +21,19 @@ public class PumpEnactResult extends Object { public String comment = ""; // Result of basal change - public Integer duration = -1; // duration set [minutes] - public Double absolute = -1d; // absolute rate [U/h] , isPercent = false - public Integer percent = -1; // percent of current basal [%] (100% = current basal), isPercent = true + public int duration = -1; // duration set [minutes] + public double absolute = -1d; // absolute rate [U/h] , isPercent = false + public int percent = -1; // percent of current basal [%] (100% = current basal), isPercent = true public boolean isPercent = false; // if true percent is used, otherwise absolute public boolean isTempCancel = false; // if true we are caceling temp basal // Result of treatment delivery - public Double bolusDelivered = 0d; // real value of delivered insulin - public Double carbsDelivered = 0d; // real value of delivered carbs + public double bolusDelivered = 0d; // real value of delivered insulin + public double carbsDelivered = 0d; // real value of delivered carbs public boolean queued = false; public PumpEnactResult success(boolean success) { - this.success = success; + this.success = success; return this; } @@ -47,16 +47,21 @@ public class PumpEnactResult extends Object { return this; } - public PumpEnactResult duration(Integer duration) { + public PumpEnactResult duration(int duration) { this.duration = duration; return this; } - public PumpEnactResult absolute(Double absolute) { + public PumpEnactResult absolute(double absolute) { this.absolute = absolute; return this; } + public PumpEnactResult percent(int percent) { + this.percent = percent; + return this; + } + public PumpEnactResult isPercent(boolean isPercent) { this.isPercent = isPercent; return this; @@ -67,12 +72,12 @@ public class PumpEnactResult extends Object { return this; } - public PumpEnactResult bolusDelivered(Double bolusDelivered) { + public PumpEnactResult bolusDelivered(double bolusDelivered) { this.bolusDelivered = bolusDelivered; return this; } - public PumpEnactResult carbsDelivered(Double carbsDelivered) { + public PumpEnactResult carbsDelivered(double carbsDelivered) { this.carbsDelivered = carbsDelivered; return this; } @@ -82,72 +87,96 @@ public class PumpEnactResult extends Object { return this; } - public String log() { - return "Success: " + success + " Enacted: " + enacted + " Comment: " + comment + " Duration: " + duration + " Absolute: " + absolute + " Percent: " + percent + " IsPercent: " + isPercent + " Queued: " + queued; + public String log() { + return "Success: " + success + + " Enacted: " + enacted + + " Comment: " + comment + + " Duration: " + duration + + " Absolute: " + absolute + + " Percent: " + percent + + " IsPercent: " + isPercent + + " IsTempCancel: " + isTempCancel + + " bolusDelivered: " + bolusDelivered + + " carbsDelivered: " + carbsDelivered + + " Queued: " + queued; } public String toString() { - String ret = MainApp.sResources.getString(R.string.success) + ": " + success; + String ret = MainApp.gs(R.string.success) + ": " + success; if (enacted) { - if (isTempCancel) { - ret += "\n" + MainApp.sResources.getString(R.string.enacted) + ": " + enacted; - ret += "\n" + MainApp.sResources.getString(R.string.comment) + ": " + comment + "\n" + - MainApp.sResources.getString(R.string.canceltemp); + if (bolusDelivered > 0) { + ret += "\n" + MainApp.gs(R.string.enacted) + ": " + enacted; + ret += "\n" + MainApp.gs(R.string.comment) + ": " + comment; + ret += "\n" + MainApp.gs(R.string.smb_shortname) + + ": " + bolusDelivered + " " + MainApp.gs(R.string.insulin_unit_shortname); + } else if (isTempCancel) { + ret += "\n" + MainApp.gs(R.string.enacted) + ": " + enacted; + if (!comment.isEmpty()) + ret += "\n" + MainApp.gs(R.string.comment) + ": " + comment; + ret += "\n" + MainApp.gs(R.string.canceltemp); } else if (isPercent) { - ret += "\n" + MainApp.sResources.getString(R.string.enacted) + ": " + enacted; - ret += "\n" + MainApp.sResources.getString(R.string.comment) + ": " + comment; - ret += "\n" + MainApp.sResources.getString(R.string.duration) + ": " + duration + " min"; - ret += "\n" + MainApp.sResources.getString(R.string.percent) + ": " + percent + "%"; + ret += "\n" + MainApp.gs(R.string.enacted) + ": " + enacted; + if (!comment.isEmpty()) + ret += "\n" + MainApp.gs(R.string.comment) + ": " + comment; + ret += "\n" + MainApp.gs(R.string.duration) + ": " + duration + " min"; + ret += "\n" + MainApp.gs(R.string.percent) + ": " + percent + "%"; } else { - ret += "\n" + MainApp.sResources.getString(R.string.enacted) + ": " + enacted; - ret += "\n" + MainApp.sResources.getString(R.string.comment) + ": " + comment; - ret += "\n" + MainApp.sResources.getString(R.string.duration) + ": " + duration + " min"; - ret += "\n" + MainApp.sResources.getString(R.string.absolute) + ": " + absolute + " U/h"; + ret += "\n" + MainApp.gs(R.string.enacted) + ": " + enacted; + if (!comment.isEmpty()) + ret += "\n" + MainApp.gs(R.string.comment) + ": " + comment; + ret += "\n" + MainApp.gs(R.string.duration) + ": " + duration + " min"; + ret += "\n" + MainApp.gs(R.string.absolute) + ": " + absolute + " U/h"; } } else { - ret += "\n" + MainApp.sResources.getString(R.string.comment) + ": " + comment; + ret += "\n" + MainApp.gs(R.string.comment) + ": " + comment; } return ret; } - public Spanned toSpanned() { - String ret = MainApp.sResources.getString(R.string.success) + ": " + success; + public String toHtml() { + String ret = "" + MainApp.gs(R.string.success) + ": " + success; if (queued) { - ret = MainApp.sResources.getString(R.string.waitingforpumpresult); + ret = MainApp.gs(R.string.waitingforpumpresult); } else if (enacted) { - if (isTempCancel) { - ret += "
" + MainApp.sResources.getString(R.string.enacted) + ": " + enacted; - ret += "
" + MainApp.sResources.getString(R.string.comment) + ": " + comment + - "
" + MainApp.sResources.getString(R.string.canceltemp); - } else if (isPercent) { - ret += "
" + MainApp.sResources.getString(R.string.enacted) + ": " + enacted; - ret += "
" + MainApp.sResources.getString(R.string.comment) + ": " + comment; - ret += "
" + MainApp.sResources.getString(R.string.duration) + ": " + duration + " min"; - ret += "
" + MainApp.sResources.getString(R.string.percent) + ": " + percent + "%"; - } else { - ret += "
" + MainApp.sResources.getString(R.string.enacted) + ": " + enacted; - ret += "
" + MainApp.sResources.getString(R.string.comment) + ": " + comment; - ret += "
" + MainApp.sResources.getString(R.string.duration) + ": " + duration + " min"; - ret += "
" + MainApp.sResources.getString(R.string.absolute) + ": " + DecimalFormatter.to2Decimal(absolute) + " U/h"; + if (bolusDelivered > 0) { + ret += "
" + MainApp.gs(R.string.enacted) + ": " + enacted; + if (!comment.isEmpty()) + ret += "
" + MainApp.gs(R.string.comment) + ": " + comment; + ret += "
" + MainApp.gs(R.string.smb_shortname) + ": " + bolusDelivered + " " + MainApp.gs(R.string.insulin_unit_shortname); + } else if (isTempCancel) { + ret += "
" + MainApp.gs(R.string.enacted) + ": " + enacted; + ret += "
" + MainApp.gs(R.string.comment) + ": " + comment + + "
" + MainApp.gs(R.string.canceltemp); + } else if (isPercent && percent != -1) { + ret += "
" + MainApp.gs(R.string.enacted) + ": " + enacted; + if (!comment.isEmpty()) + ret += "
" + MainApp.gs(R.string.comment) + ": " + comment; + ret += "
" + MainApp.gs(R.string.duration) + ": " + duration + " min"; + ret += "
" + MainApp.gs(R.string.percent) + ": " + percent + "%"; + } else if (absolute != -1) { + ret += "
" + MainApp.gs(R.string.enacted) + ": " + enacted; + if (!comment.isEmpty()) + ret += "
" + MainApp.gs(R.string.comment) + ": " + comment; + ret += "
" + MainApp.gs(R.string.duration) + ": " + duration + " min"; + ret += "
" + MainApp.gs(R.string.absolute) + ": " + DecimalFormatter.to2Decimal(absolute) + " U/h"; } } else { - ret += "
" + MainApp.sResources.getString(R.string.comment) + ": " + comment; + ret += "
" + MainApp.gs(R.string.comment) + ": " + comment; } - return Html.fromHtml(ret); + return ret; } - public PumpEnactResult() { - } - - public JSONObject json() { + public JSONObject json(Profile profile) { JSONObject result = new JSONObject(); try { - if (isTempCancel) { + if (bolusDelivered > 0) { + result.put("smb", bolusDelivered); + } else if (isTempCancel) { result.put("rate", 0); result.put("duration", 0); } else if (isPercent) { // Nightscout is expecting absolute value - Double abs = Round.roundTo(MainApp.getConfigBuilder().getProfile().getBasal() * percent / 100, 0.01); + Double abs = Round.roundTo(profile.getBasal() * percent / 100, 0.01); result.put("rate", abs); result.put("duration", duration); } else { diff --git a/app/src/main/java/info/nightscout/androidaps/data/QuickWizard.java b/app/src/main/java/info/nightscout/androidaps/data/QuickWizard.java index 587d7f86a2..25c768cdda 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/QuickWizard.java +++ b/app/src/main/java/info/nightscout/androidaps/data/QuickWizard.java @@ -10,6 +10,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import info.nightscout.androidaps.MainApp; +import info.nightscout.utils.SP; /** * Created by mike on 12.10.2016. @@ -25,10 +26,7 @@ public class QuickWizard { } public void save() { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(MainApp.instance().getApplicationContext()); - SharedPreferences.Editor editor = preferences.edit(); - editor.putString("QuickWizard", storage.toString()); - editor.apply(); + SP.putString("QuickWizard", storage.toString()); } public int size() { diff --git a/app/src/main/java/info/nightscout/androidaps/data/QuickWizardEntry.java b/app/src/main/java/info/nightscout/androidaps/data/QuickWizardEntry.java index 35a7265f2a..04f297d7a9 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/QuickWizardEntry.java +++ b/app/src/main/java/info/nightscout/androidaps/data/QuickWizardEntry.java @@ -7,15 +7,14 @@ import org.slf4j.LoggerFactory; import java.util.Date; -import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.TempTarget; import info.nightscout.androidaps.interfaces.TreatmentsInterface; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensData; +import info.nightscout.androidaps.plugins.IobCobCalculator.CobInfo; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.Loop.LoopPlugin; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.BolusWizard; import info.nightscout.utils.DateUtil; import info.nightscout.utils.SP; @@ -80,14 +79,10 @@ public class QuickWizardEntry { // COB double cob = 0d; - AutosensData autosensData; - if (_synchronized) - autosensData = IobCobCalculatorPlugin.getLastAutosensDataSynchronized("QuickWizard COB"); - else - autosensData = IobCobCalculatorPlugin.getLastAutosensData("QuickWizard COB"); - - if (autosensData != null && useCOB() == YES) { - cob = autosensData.cob; + if (useCOB() == YES) { + CobInfo cobInfo = IobCobCalculatorPlugin.getPlugin().getCobInfo(_synchronized, "QuickWizard COB"); + if (cobInfo.displayCob != null) + cob = cobInfo.displayCob; } // Temp target @@ -102,7 +97,7 @@ public class QuickWizardEntry { } // Basal IOB - TreatmentsInterface treatments = MainApp.getConfigBuilder(); + TreatmentsInterface treatments = TreatmentsPlugin.getPlugin(); treatments.updateTotalIOBTempBasals(); IobTotal basalIob = treatments.getLastCalculationTempBasals().round(); boolean basalIOB = false; @@ -119,8 +114,8 @@ public class QuickWizardEntry { if (useSuperBolus() == YES && SP.getBoolean(R.string.key_usesuperbolus, false)) { superBolus = true; } - final LoopPlugin activeloop = ConfigBuilderPlugin.getActiveLoop(); - if (activeloop != null && activeloop.isEnabled(activeloop.getType()) && activeloop.isSuperBolus()) + final LoopPlugin loopPlugin = LoopPlugin.getPlugin(); + if (loopPlugin.isEnabled(loopPlugin.getType()) && loopPlugin.isSuperBolus()) superBolus = false; // Trend diff --git a/app/src/main/java/info/nightscout/androidaps/db/BgReading.java b/app/src/main/java/info/nightscout/androidaps/db/BgReading.java index c8d5295811..20b9040065 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/BgReading.java +++ b/app/src/main/java/info/nightscout/androidaps/db/BgReading.java @@ -17,7 +17,6 @@ import info.nightscout.androidaps.plugins.NSClientInternal.data.NSSgv; import info.nightscout.androidaps.plugins.Overview.OverviewPlugin; import info.nightscout.androidaps.plugins.Overview.graphExtensions.DataPointWithLabelInterface; import info.nightscout.androidaps.plugins.Overview.graphExtensions.PointsWithLabelGraphSeries; -import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.SP; @@ -43,7 +42,11 @@ public class BgReading implements DataPointWithLabelInterface { @DatabaseField public String _id = null; // NS _id - public boolean isPrediction = false; // true when drawing predictions as bg points + public boolean isCOBPrediction = false; // true when drawing predictions as bg points (COB) + public boolean isaCOBPrediction = false; // true when drawing predictions as bg points (aCOB) + public boolean isIOBPrediction = false; // true when drawing predictions as bg points (IOB) + public boolean isUAMPrediction = false; // true when drawing predictions as bg points (UAM) + public boolean isZTPrediction = false; // true when drawing predictions as bg points (ZT) public BgReading() { } @@ -184,13 +187,15 @@ public class BgReading implements DataPointWithLabelInterface { @Override public PointsWithLabelGraphSeries.Shape getShape() { - return PointsWithLabelGraphSeries.Shape.POINT; + if (isPrediction()) + return PointsWithLabelGraphSeries.Shape.PREDICTION; + else + return PointsWithLabelGraphSeries.Shape.BG; } @Override public float getSize() { - boolean isTablet = MainApp.sResources.getBoolean(R.bool.isTablet); - return isTablet ? 8 : 5; + return 1; } @Override @@ -205,7 +210,7 @@ public class BgReading implements DataPointWithLabelInterface { highLine = Profile.fromMgdlToUnits(OverviewPlugin.bgTargetHigh, units); } int color = MainApp.sResources.getColor(R.color.inrange); - if (isPrediction) + if (isPrediction()) color = MainApp.sResources.getColor(R.color.prediction); else if (valueToUnits(units) < lowLine) color = MainApp.sResources.getColor(R.color.low); @@ -214,4 +219,23 @@ public class BgReading implements DataPointWithLabelInterface { return color; } + @Override + public int getSecondColor() { + if (isIOBPrediction) + return MainApp.sResources.getColor(R.color.iob); + if (isCOBPrediction) + return MainApp.sResources.getColor(R.color.cob); + if (isaCOBPrediction) + return 0x80FFFFFF & MainApp.sResources.getColor(R.color.cob); + if (isUAMPrediction) + return MainApp.sResources.getColor(R.color.uam); + if (isZTPrediction) + return MainApp.sResources.getColor(R.color.zt); + return R.color.mdtp_white; + } + + private boolean isPrediction() { + return isaCOBPrediction || isCOBPrediction || isIOBPrediction || isUAMPrediction || isZTPrediction; + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/db/CareportalEvent.java b/app/src/main/java/info/nightscout/androidaps/db/CareportalEvent.java index 8c55034c9f..b0f69144c5 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/CareportalEvent.java +++ b/app/src/main/java/info/nightscout/androidaps/db/CareportalEvent.java @@ -5,6 +5,7 @@ import android.graphics.Color; import com.j256.ormlite.field.DatabaseField; import com.j256.ormlite.table.DatabaseTable; +import org.apache.commons.lang3.StringUtils; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; @@ -97,6 +98,14 @@ public class CareportalEvent implements DataPointWithLabelInterface { return diff.get(TimeUnit.DAYS) + " " + MainApp.sResources.getString(R.string.days) + " " + diff.get(TimeUnit.HOURS) + " " + MainApp.sResources.getString(R.string.hours); } + public boolean isOlderThan(double hours) { + Map diff = computeDiff(date, System.currentTimeMillis()); + if(diff.get(TimeUnit.DAYS)*24 + diff.get(TimeUnit.HOURS) > hours) + return true; + else + return false; + } + public String log() { return "CareportalEvent{" + "date= " + date + @@ -184,7 +193,7 @@ public class CareportalEvent implements DataPointWithLabelInterface { try { JSONObject object = new JSONObject(json); if (object.has("notes")) - return object.getString("notes"); + return StringUtils.abbreviate(object.getString("notes"), 40); } catch (JSONException e) { log.error("Unhandled exception", e); } @@ -253,4 +262,10 @@ public class CareportalEvent implements DataPointWithLabelInterface { return Color.GRAY; return Color.GRAY; } + + @Override + public int getSecondColor() { + return 0; + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java index 082f872d20..7174c19ec4 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java @@ -30,12 +30,10 @@ import java.util.concurrent.TimeUnit; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.events.EventCareportalEventChange; import info.nightscout.androidaps.events.EventExtendedBolusChange; -import info.nightscout.androidaps.events.EventFoodDatabaseChanged; import info.nightscout.androidaps.events.EventNewBG; import info.nightscout.androidaps.events.EventProfileSwitchChange; import info.nightscout.androidaps.events.EventRefreshOverview; @@ -44,17 +42,23 @@ import info.nightscout.androidaps.events.EventReloadTempBasalData; import info.nightscout.androidaps.events.EventReloadTreatmentData; import info.nightscout.androidaps.events.EventTempBasalChange; import info.nightscout.androidaps.events.EventTempTargetChange; -import info.nightscout.androidaps.events.EventTreatmentChange; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventNewHistoryData; -import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.PumpDanaR.activities.DanaRNSHistorySync; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.RecordTypes; import info.nightscout.androidaps.plugins.PumpVirtual.VirtualPumpPlugin; -import info.nightscout.utils.DateUtil; +import info.nightscout.utils.JsonHelper; import info.nightscout.utils.NSUpload; import info.nightscout.utils.PercentageSplitter; +import info.nightscout.utils.ToastUtils; +/** + * 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). + *

+ * 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 { private static Logger log = LoggerFactory.getLogger(DatabaseHelper.class); @@ -63,23 +67,19 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { public static final String DATABASE_TEMPORARYBASALS = "TemporaryBasals"; public static final String DATABASE_EXTENDEDBOLUSES = "ExtendedBoluses"; public static final String DATABASE_TEMPTARGETS = "TempTargets"; - public static final String DATABASE_TREATMENTS = "Treatments"; public static final String DATABASE_DANARHISTORY = "DanaRHistory"; public static final String DATABASE_DBREQUESTS = "DBRequests"; public static final String DATABASE_CAREPORTALEVENTS = "CareportalEvents"; public static final String DATABASE_PROFILESWITCHES = "ProfileSwitches"; - public static final String DATABASE_FOODS = "Foods"; + public static final String DATABASE_TDDS = "TDDs"; private static final int DATABASE_VERSION = 8; - private static Long earliestDataChange = null; + public static Long earliestDataChange = null; private static final ScheduledExecutorService bgWorker = Executors.newSingleThreadScheduledExecutor(); private static ScheduledFuture scheduledBgPost = null; - private static final ScheduledExecutorService treatmentsWorker = Executors.newSingleThreadScheduledExecutor(); - private static ScheduledFuture scheduledTratmentPost = null; - private static final ScheduledExecutorService tempBasalsWorker = Executors.newSingleThreadScheduledExecutor(); private static ScheduledFuture scheduledTemBasalsPost = null; @@ -95,7 +95,8 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { private static final ScheduledExecutorService profileSwitchEventWorker = Executors.newSingleThreadScheduledExecutor(); private static ScheduledFuture scheduledProfileSwitchEventPost = null; - public FoodHelper foodHelper = new FoodHelper(this); + private int oldVersion = 0; + private int newVersion = 0; public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); @@ -108,7 +109,6 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { try { log.info("onCreate"); TableUtils.createTableIfNotExists(connectionSource, TempTarget.class); - TableUtils.createTableIfNotExists(connectionSource, Treatment.class); TableUtils.createTableIfNotExists(connectionSource, BgReading.class); TableUtils.createTableIfNotExists(connectionSource, DanaRHistoryRecord.class); TableUtils.createTableIfNotExists(connectionSource, DbRequest.class); @@ -116,7 +116,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { TableUtils.createTableIfNotExists(connectionSource, ExtendedBolus.class); TableUtils.createTableIfNotExists(connectionSource, CareportalEvent.class); TableUtils.createTableIfNotExists(connectionSource, ProfileSwitch.class); - TableUtils.createTableIfNotExists(connectionSource, Food.class); + TableUtils.createTableIfNotExists(connectionSource, TDD.class); } catch (SQLException e) { log.error("Can't create database", e); throw new RuntimeException(e); @@ -126,14 +126,14 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { @Override public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) { try { + this.oldVersion = oldVersion; + this.newVersion = newVersion; + if (oldVersion == 7 && newVersion == 8) { log.debug("Upgrading database from v7 to v8"); - TableUtils.dropTable(connectionSource, Treatment.class, true); - TableUtils.createTableIfNotExists(connectionSource, Treatment.class); } else { log.info(DatabaseHelper.class.getName(), "onUpgrade"); TableUtils.dropTable(connectionSource, TempTarget.class, true); - TableUtils.dropTable(connectionSource, Treatment.class, true); TableUtils.dropTable(connectionSource, BgReading.class, true); TableUtils.dropTable(connectionSource, DanaRHistoryRecord.class, true); TableUtils.dropTable(connectionSource, DbRequest.class, true); @@ -141,7 +141,6 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { TableUtils.dropTable(connectionSource, ExtendedBolus.class, true); TableUtils.dropTable(connectionSource, CareportalEvent.class, true); TableUtils.dropTable(connectionSource, ProfileSwitch.class, true); - TableUtils.dropTable(connectionSource, Food.class, true); onCreate(database, connectionSource); } } catch (SQLException e) { @@ -150,6 +149,14 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } } + public int getOldVersion() { + return oldVersion; + } + + public int getNewVersion() { + return newVersion; + } + /** * Close the database connections and clear any cached DAOs. */ @@ -158,40 +165,6 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { super.close(); } - public void cleanUpDatabases() { - // TODO: call it somewhere - log.debug("Before BgReadings size: " + DatabaseUtils.queryNumEntries(getReadableDatabase(), DATABASE_BGREADINGS)); - getWritableDatabase().delete(DATABASE_BGREADINGS, "date" + " < '" + (System.currentTimeMillis() - Constants.hoursToKeepInDatabase * 60 * 60 * 1000L) + "'", null); - log.debug("After BgReadings size: " + DatabaseUtils.queryNumEntries(getReadableDatabase(), DATABASE_BGREADINGS)); - - log.debug("Before TempTargets size: " + DatabaseUtils.queryNumEntries(getReadableDatabase(), DATABASE_TEMPTARGETS)); - getWritableDatabase().delete(DATABASE_TEMPTARGETS, "date" + " < '" + (System.currentTimeMillis() - Constants.hoursToKeepInDatabase * 60 * 60 * 1000L) + "'", null); - log.debug("After TempTargets size: " + DatabaseUtils.queryNumEntries(getReadableDatabase(), DATABASE_TEMPTARGETS)); - - log.debug("Before Treatments size: " + DatabaseUtils.queryNumEntries(getReadableDatabase(), DATABASE_TREATMENTS)); - getWritableDatabase().delete(DATABASE_TREATMENTS, "date" + " < '" + (System.currentTimeMillis() - Constants.hoursToKeepInDatabase * 60 * 60 * 1000L) + "'", null); - log.debug("After Treatments size: " + DatabaseUtils.queryNumEntries(getReadableDatabase(), DATABASE_TREATMENTS)); - - log.debug("Before History size: " + DatabaseUtils.queryNumEntries(getReadableDatabase(), DATABASE_DANARHISTORY)); - getWritableDatabase().delete(DATABASE_DANARHISTORY, "recordDate" + " < '" + (System.currentTimeMillis() - Constants.daysToKeepHistoryInDatabase * 24 * 60 * 60 * 1000L) + "'", null); - log.debug("After History size: " + DatabaseUtils.queryNumEntries(getReadableDatabase(), DATABASE_DANARHISTORY)); - - log.debug("Before TemporaryBasals size: " + DatabaseUtils.queryNumEntries(getReadableDatabase(), DATABASE_TEMPORARYBASALS)); - getWritableDatabase().delete(DATABASE_TEMPORARYBASALS, "recordDate" + " < '" + (System.currentTimeMillis() - Constants.daysToKeepHistoryInDatabase * 24 * 60 * 60 * 1000L) + "'", null); - log.debug("After TemporaryBasals size: " + DatabaseUtils.queryNumEntries(getReadableDatabase(), DATABASE_TEMPORARYBASALS)); - - log.debug("Before ExtendedBoluses size: " + DatabaseUtils.queryNumEntries(getReadableDatabase(), DATABASE_EXTENDEDBOLUSES)); - getWritableDatabase().delete(DATABASE_EXTENDEDBOLUSES, "recordDate" + " < '" + (System.currentTimeMillis() - Constants.daysToKeepHistoryInDatabase * 24 * 60 * 60 * 1000L) + "'", null); - log.debug("After ExtendedBoluses size: " + DatabaseUtils.queryNumEntries(getReadableDatabase(), DATABASE_EXTENDEDBOLUSES)); - - log.debug("Before CareportalEvent size: " + DatabaseUtils.queryNumEntries(getReadableDatabase(), DATABASE_CAREPORTALEVENTS)); - getWritableDatabase().delete(DATABASE_CAREPORTALEVENTS, "recordDate" + " < '" + (System.currentTimeMillis() - Constants.daysToKeepHistoryInDatabase * 24 * 60 * 60 * 1000L) + "'", null); - log.debug("After CareportalEvent size: " + DatabaseUtils.queryNumEntries(getReadableDatabase(), DATABASE_CAREPORTALEVENTS)); - - log.debug("Before ProfileSwitch size: " + DatabaseUtils.queryNumEntries(getReadableDatabase(), DATABASE_PROFILESWITCHES)); - getWritableDatabase().delete(DATABASE_PROFILESWITCHES, "recordDate" + " < '" + (System.currentTimeMillis() - Constants.daysToKeepHistoryInDatabase * 24 * 60 * 60 * 1000L) + "'", null); - log.debug("After ProfileSwitch size: " + DatabaseUtils.queryNumEntries(getReadableDatabase(), DATABASE_PROFILESWITCHES)); - } public long size(String database) { return DatabaseUtils.queryNumEntries(getReadableDatabase(), database); @@ -202,7 +175,6 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { public void resetDatabases() { try { TableUtils.dropTable(connectionSource, TempTarget.class, true); - TableUtils.dropTable(connectionSource, Treatment.class, true); TableUtils.dropTable(connectionSource, BgReading.class, true); TableUtils.dropTable(connectionSource, DanaRHistoryRecord.class, true); TableUtils.dropTable(connectionSource, DbRequest.class, true); @@ -210,8 +182,8 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { TableUtils.dropTable(connectionSource, ExtendedBolus.class, true); TableUtils.dropTable(connectionSource, CareportalEvent.class, true); TableUtils.dropTable(connectionSource, ProfileSwitch.class, true); + TableUtils.dropTable(connectionSource, TDD.class, true); TableUtils.createTableIfNotExists(connectionSource, TempTarget.class); - TableUtils.createTableIfNotExists(connectionSource, Treatment.class); TableUtils.createTableIfNotExists(connectionSource, BgReading.class); TableUtils.createTableIfNotExists(connectionSource, DanaRHistoryRecord.class); TableUtils.createTableIfNotExists(connectionSource, DbRequest.class); @@ -219,20 +191,18 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { TableUtils.createTableIfNotExists(connectionSource, ExtendedBolus.class); TableUtils.createTableIfNotExists(connectionSource, CareportalEvent.class); TableUtils.createTableIfNotExists(connectionSource, ProfileSwitch.class); - foodHelper.resetFood(); + TableUtils.createTableIfNotExists(connectionSource, TDD.class); updateEarliestDataChange(0); } catch (SQLException e) { log.error("Unhandled exception", e); } VirtualPumpPlugin.setFakingStatus(true); - scheduleBgChange(); // trigger refresh + scheduleBgChange(null); // trigger refresh scheduleTemporaryBasalChange(); - scheduleTreatmentChange(); scheduleExtendedBolusChange(); scheduleTemporaryTargetChange(); scheduleCareportalEventChange(); scheduleProfileSwitchChange(); - foodHelper.scheduleFoodChange(); new java.util.Timer().schedule( new java.util.TimerTask() { @Override @@ -244,17 +214,6 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { ); } - public void resetTreatments() { - try { - TableUtils.dropTable(connectionSource, Treatment.class, true); - TableUtils.createTableIfNotExists(connectionSource, Treatment.class); - updateEarliestDataChange(0); - } catch (SQLException e) { - log.error("Unhandled exception", e); - } - scheduleTreatmentChange(); - } - public void resetTempTargets() { try { TableUtils.dropTable(connectionSource, TempTarget.class, true); @@ -308,16 +267,21 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { scheduleProfileSwitchChange(); } + public void resetTDDs() { + try { + TableUtils.dropTable(connectionSource, TDD.class, true); + TableUtils.createTableIfNotExists(connectionSource, TDD.class); + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + } + // ------------------ getDao ------------------------------------------- private Dao getDaoTempTargets() throws SQLException { return getDao(TempTarget.class); } - private Dao getDaoTreatments() throws SQLException { - return getDao(Treatment.class); - } - private Dao getDaoBgReadings() throws SQLException { return getDao(BgReading.class); } @@ -326,6 +290,10 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { return getDao(DanaRHistoryRecord.class); } + private Dao getDaoTDD() throws SQLException { + return getDao(TDD.class); + } + private Dao getDaoDbRequest() throws SQLException { return getDao(DbRequest.class); } @@ -346,7 +314,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { return getDao(ProfileSwitch.class); } - public long roundDateToSec(long date) { + public static long roundDateToSec(long date) { return date - date % 1000; } // ------------------- BgReading handling ----------------------- @@ -358,7 +326,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { if (old == null) { getDaoBgReadings().create(bgReading); log.debug("BG: New record from: " + from + " " + bgReading.toString()); - scheduleBgChange(); + scheduleBgChange(bgReading); return true; } if (!old.isEqual(bgReading)) { @@ -366,7 +334,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { old.copyFrom(bgReading); getDaoBgReadings().update(old); log.debug("BG: Updating record from: " + from + " New data: " + old.toString()); - scheduleBgChange(); + scheduleBgChange(bgReading); return false; } } catch (SQLException e) { @@ -380,15 +348,15 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { try { getDaoBgReadings().update(bgReading); } catch (SQLException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } } - private static void scheduleBgChange() { + private static void scheduleBgChange(@Nullable final BgReading bgReading) { class PostRunnable implements Runnable { public void run() { log.debug("Firing EventNewBg"); - MainApp.bus().post(new EventNewBG()); + MainApp.bus().post(new EventNewBG(bgReading)); scheduledBgPost = null; } } @@ -403,8 +371,8 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } /* - * Return last BgReading from database or null if db is empty - */ + * Return last BgReading from database or null if db is empty + */ @Nullable public static BgReading lastBg() { List bgList = null; @@ -428,9 +396,9 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } /* - * Return bg reading if not old ( <9 min ) - * or null if older - */ + * Return bg reading if not old ( <9 min ) + * or null if older + */ @Nullable public static BgReading actualBg() { BgReading lastBg = lastBg(); @@ -479,6 +447,33 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { return new ArrayList(); } + // ------------------- TDD handling ----------------------- + public void createOrUpdateTDD(TDD tdd) { + try { + Dao dao = getDaoTDD(); + dao.createOrUpdate(tdd); + } catch (SQLException e) { + ToastUtils.showToastInUiThread(MainApp.instance(), "createOrUpdate-Exception"); + log.error("Unhandled exception", e); + } + } + + public List getTDDs() { + List tddList; + try { + QueryBuilder queryBuilder = getDaoTDD().queryBuilder(); + queryBuilder.orderBy("date", false); + queryBuilder.limit(10L); + PreparedQuery preparedQuery = queryBuilder.prepare(); + tddList = getDaoTDD().query(preparedQuery); + } catch (SQLException e) { + log.error("Unhandled exception", e); + tddList = new ArrayList<>(); + } + return tddList; + } + + // ------------- DbRequests handling ------------------- public void create(DbRequest dbr) { @@ -515,7 +510,6 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { queryBuilder.limit(10L); PreparedQuery preparedQuery = queryBuilder.prepare(); List dbList = getDaoDbRequest().query(preparedQuery); - log.error("deleteDbRequestbyMongoId query size: " + dbList.size()); for (DbRequest r : dbList) { delete(r); } @@ -543,146 +537,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { // -------------------- TREATMENT HANDLING ------------------- - // return true if new record is created - public boolean createOrUpdate(Treatment treatment) { - try { - Treatment old; - treatment.date = roundDateToSec(treatment.date); - - if (treatment.source == Source.PUMP) { - // check for changed from pump change in NS - QueryBuilder queryBuilder = getDaoTreatments().queryBuilder(); - Where where = queryBuilder.where(); - where.eq("pumpId", treatment.pumpId); - PreparedQuery preparedQuery = queryBuilder.prepare(); - List trList = getDaoTreatments().query(preparedQuery); - if (trList.size() > 0) { - // do nothing, pump history record cannot be changed - return false; - } - getDaoTreatments().create(treatment); - log.debug("TREATMENT: New record from: " + Source.getString(treatment.source) + " " + treatment.toString()); - updateEarliestDataChange(treatment.date); - scheduleTreatmentChange(); - return true; - } - if (treatment.source == Source.NIGHTSCOUT) { - old = getDaoTreatments().queryForId(treatment.date); - if (old != null) { - if (!old.isEqual(treatment)) { - boolean historyChange = old.isDataChanging(treatment); - long oldDate = old.date; - getDaoTreatments().delete(old); // need to delete/create because date may change too - old.copyFrom(treatment); - getDaoTreatments().create(old); - log.debug("TREATMENT: Updating record by date from: " + Source.getString(treatment.source) + " " + old.toString()); - if (historyChange) { - updateEarliestDataChange(oldDate); - updateEarliestDataChange(old.date); - } - scheduleTreatmentChange(); - return true; - } - return false; - } - // find by NS _id - if (treatment._id != null) { - QueryBuilder queryBuilder = getDaoTreatments().queryBuilder(); - Where where = queryBuilder.where(); - where.eq("_id", treatment._id); - PreparedQuery preparedQuery = queryBuilder.prepare(); - List trList = getDaoTreatments().query(preparedQuery); - if (trList.size() > 0) { - old = trList.get(0); - if (!old.isEqual(treatment)) { - boolean historyChange = old.isDataChanging(treatment); - long oldDate = old.date; - getDaoTreatments().delete(old); // need to delete/create because date may change too - old.copyFrom(treatment); - getDaoTreatments().create(old); - log.debug("TREATMENT: Updating record by _id from: " + Source.getString(treatment.source) + " " + old.toString()); - if (historyChange) { - updateEarliestDataChange(oldDate); - updateEarliestDataChange(old.date); - } - scheduleTreatmentChange(); - return true; - } - } - } - getDaoTreatments().create(treatment); - log.debug("TREATMENT: New record from: " + Source.getString(treatment.source) + " " + treatment.toString()); - updateEarliestDataChange(treatment.date); - scheduleTreatmentChange(); - return true; - } - if (treatment.source == Source.USER) { - getDaoTreatments().create(treatment); - log.debug("TREATMENT: New record from: " + Source.getString(treatment.source) + " " + treatment.toString()); - updateEarliestDataChange(treatment.date); - scheduleTreatmentChange(); - return true; - } - } catch (SQLException e) { - log.error("Unhandled exception", e); - } - return false; - } - - public void delete(Treatment treatment) { - try { - getDaoTreatments().delete(treatment); - updateEarliestDataChange(treatment.date); - } catch (SQLException e) { - log.error("Unhandled exception", e); - } - scheduleTreatmentChange(); - } - - public void update(Treatment treatment) { - try { - getDaoTreatments().update(treatment); - updateEarliestDataChange(treatment.date); - } catch (SQLException e) { - log.error("Unhandled exception", e); - } - scheduleTreatmentChange(); - } - - public void deleteTreatmentById(String _id) { - Treatment stored = findTreatmentById(_id); - if (stored != null) { - log.debug("TREATMENT: Removing Treatment record from database: " + stored.toString()); - delete(stored); - updateEarliestDataChange(stored.date); - scheduleTreatmentChange(); - } - } - - @Nullable - public Treatment findTreatmentById(String _id) { - try { - Dao daoTreatments = getDaoTreatments(); - QueryBuilder queryBuilder = daoTreatments.queryBuilder(); - Where where = queryBuilder.where(); - where.eq("_id", _id); - queryBuilder.limit(10L); - PreparedQuery preparedQuery = queryBuilder.prepare(); - List trList = daoTreatments.query(preparedQuery); - if (trList.size() != 1) { - //log.debug("Treatment findTreatmentById query size: " + trList.size()); - return null; - } else { - //log.debug("Treatment findTreatmentById found: " + trList.get(0).log()); - return trList.get(0); - } - } catch (SQLException e) { - log.error("Unhandled exception", e); - } - return null; - } - - private void updateEarliestDataChange(long newDate) { + public static void updateEarliestDataChange(long newDate) { if (earliestDataChange == null) { earliestDataChange = newDate; return; @@ -692,73 +547,6 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } } - private static void scheduleTreatmentChange() { - class PostRunnable implements Runnable { - public void run() { - log.debug("Firing EventTreatmentChange"); - MainApp.bus().post(new EventReloadTreatmentData(new EventTreatmentChange())); - if (earliestDataChange != null) - MainApp.bus().post(new EventNewHistoryData(earliestDataChange)); - earliestDataChange = null; - scheduledTratmentPost = null; - } - } - // prepare task for execution in 1 sec - // cancel waiting task to prevent sending multiple posts - if (scheduledTratmentPost != null) - scheduledTratmentPost.cancel(false); - Runnable task = new PostRunnable(); - final int sec = 1; - scheduledTratmentPost = treatmentsWorker.schedule(task, sec, TimeUnit.SECONDS); - - } - - public List getTreatmentDataFromTime(long mills, boolean ascending) { - try { - Dao daoTreatments = getDaoTreatments(); - List treatments; - QueryBuilder queryBuilder = daoTreatments.queryBuilder(); - queryBuilder.orderBy("date", ascending); - Where where = queryBuilder.where(); - where.ge("date", mills); - PreparedQuery preparedQuery = queryBuilder.prepare(); - treatments = daoTreatments.query(preparedQuery); - return treatments; - } catch (SQLException e) { - log.error("Unhandled exception", e); - } - return new ArrayList(); - } - - public void createTreatmentFromJsonIfNotExists(JSONObject trJson) { - try { - Treatment treatment = new Treatment(); - treatment.source = Source.NIGHTSCOUT; - treatment.date = roundDateToSec(trJson.getLong("mills")); - treatment.carbs = trJson.has("carbs") ? trJson.getDouble("carbs") : 0; - treatment.insulin = trJson.has("insulin") ? trJson.getDouble("insulin") : 0d; - treatment.pumpId = trJson.has("pumpId") ? trJson.getLong("pumpId") : 0; - treatment._id = trJson.getString("_id"); - if (trJson.has("isSMB")) - treatment.isSMB = trJson.getBoolean("isSMB"); - if (trJson.has("eventType")) { - treatment.mealBolus = !trJson.get("eventType").equals("Correction Bolus"); - double carbs = treatment.carbs; - if (trJson.has("boluscalc")) { - JSONObject boluscalc = trJson.getJSONObject("boluscalc"); - if (boluscalc.has("carbs")) { - carbs = Math.max(boluscalc.getDouble("carbs"), carbs); - } - } - if (carbs <= 0) - treatment.mealBolus = false; - } - createOrUpdate(treatment); - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - } - // ---------------- TempTargets handling --------------- public List getTemptargetsDataFromTime(long mills, boolean ascending) { @@ -876,15 +664,15 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { public void createTemptargetFromJsonIfNotExists(JSONObject trJson) { try { - String units = MainApp.getConfigBuilder().getProfileUnits(); - TempTarget tempTarget = new TempTarget(); - tempTarget.date = trJson.getLong("mills"); - tempTarget.durationInMinutes = trJson.getInt("duration"); - tempTarget.low = Profile.toMgdl(trJson.getDouble("targetBottom"), units); - tempTarget.high = Profile.toMgdl(trJson.getDouble("targetTop"), units); - tempTarget.reason = trJson.getString("reason"); - tempTarget._id = trJson.getString("_id"); - tempTarget.source = Source.NIGHTSCOUT; + String units = JsonHelper.safeGetString(trJson, "units", MainApp.getConfigBuilder().getProfileUnits()); + TempTarget tempTarget = new TempTarget() + .date(trJson.getLong("mills")) + .duration(trJson.getInt("duration")) + .low(Profile.toMgdl(trJson.getDouble("targetBottom"), units)) + .high(Profile.toMgdl(trJson.getDouble("targetTop"), units)) + .reason(trJson.getString("reason")) + ._id(trJson.getString("_id")) + .source(Source.NIGHTSCOUT); createOrUpdate(tempTarget); } catch (JSONException e) { log.error("Unhandled exception", e); @@ -924,6 +712,12 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { public void createOrUpdate(DanaRHistoryRecord record) { try { getDaoDanaRHistory().createOrUpdate(record); + + //If it is a TDD, store it for stats also. + if (record.recordCode == RecordTypes.RECORD_TYPE_DAILY) { + createOrUpdateTDD(new TDD(record.recordDate, record.recordDailyBolus, record.recordDailyBasal, 0)); + } + } catch (SQLException e) { log.error("Unhandled exception", e); } @@ -1153,10 +947,10 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } createOrUpdate(extendedBolus); } else { - TemporaryBasal tempBasal = new TemporaryBasal(); - tempBasal.date = trJson.getLong("mills"); - tempBasal.source = Source.NIGHTSCOUT; - tempBasal.pumpId = trJson.has("pumpId") ? trJson.getLong("pumpId") : 0; + TemporaryBasal tempBasal = new TemporaryBasal() + .date(trJson.getLong("mills")) + .source(Source.NIGHTSCOUT) + .pumpId(trJson.has("pumpId") ? trJson.getLong("pumpId") : 0); if (trJson.has("duration")) { tempBasal.durationInMinutes = trJson.getInt("duration"); } @@ -1360,37 +1154,10 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } */ - public void createExtendedBolusFromJsonIfNotExists(JSONObject trJson) { - try { - QueryBuilder queryBuilder = null; - queryBuilder = getDaoExtendedBolus().queryBuilder(); - Where where = queryBuilder.where(); - where.eq("_id", trJson.getString("_id")).or().eq("date", trJson.getLong("mills")); - PreparedQuery preparedQuery = queryBuilder.prepare(); - List list = getDaoExtendedBolus().query(preparedQuery); - ExtendedBolus extendedBolus; - if (list.size() == 0) { - extendedBolus = new ExtendedBolus(); - extendedBolus.source = Source.NIGHTSCOUT; - if (Config.logIncommingData) - log.debug("Adding ExtendedBolus record to database: " + trJson.toString()); - // Record does not exists. add - } else if (list.size() == 1) { - extendedBolus = list.get(0); - if (Config.logIncommingData) - log.debug("Updating ExtendedBolus record in database: " + trJson.toString()); - } else { - log.error("Something went wrong"); - return; - } - extendedBolus.date = trJson.getLong("mills"); - extendedBolus.durationInMinutes = trJson.has("duration") ? trJson.getInt("duration") : 0; - extendedBolus.insulin = trJson.getDouble("relative"); - extendedBolus._id = trJson.getString("_id"); + public void createExtendedBolusFromJsonIfNotExists(JSONObject json) { + ExtendedBolus extendedBolus = ExtendedBolus.createFromJson(json); + if (extendedBolus != null) createOrUpdate(extendedBolus); - } catch (SQLException | JSONException e) { - log.error("Unhandled exception", e); - } } private static void scheduleExtendedBolusChange() { @@ -1436,6 +1203,15 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { scheduleCareportalEventChange(); } + public CareportalEvent getCareportalEventFromTimestamp(long timestamp) { + try { + return getDaoCareportalEvents().queryForId(timestamp); + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return null; + } + @Nullable public CareportalEvent getLastCareportalEvent(String event) { try { @@ -1489,7 +1265,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { public void deleteCareportalEventById(String _id) { try { - QueryBuilder queryBuilder = null; + QueryBuilder queryBuilder; queryBuilder = getDaoCareportalEvents().queryBuilder(); Where where = queryBuilder.where(); where.eq("_id", _id); @@ -1512,7 +1288,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { public void createCareportalEventFromJsonIfNotExists(JSONObject trJson) { try { - QueryBuilder queryBuilder = null; + QueryBuilder queryBuilder; queryBuilder = getDaoCareportalEvents().queryBuilder(); Where where = queryBuilder.where(); where.eq("_id", trJson.getString("_id")).or().eq("date", trJson.getLong("mills")); @@ -1569,14 +1345,14 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { List profileSwitches; QueryBuilder queryBuilder = daoProfileSwitch.queryBuilder(); queryBuilder.orderBy("date", ascending); - queryBuilder.limit(20L); + queryBuilder.limit(100L); PreparedQuery preparedQuery = queryBuilder.prepare(); profileSwitches = daoProfileSwitch.query(preparedQuery); return profileSwitches; } catch (SQLException e) { log.error("Unhandled exception", e); } - return new ArrayList(); + return new ArrayList<>(); } public boolean createOrUpdate(ProfileSwitch profileSwitch) { @@ -1693,7 +1469,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { if (trJson.has("profileJson")) profileSwitch.profileJson = trJson.getString("profileJson"); else { - ProfileStore store = ConfigBuilderPlugin.getActiveProfileInterface().getProfile(); + ProfileStore store = MainApp.getConfigBuilder().getActiveProfileInterface().getProfile(); Profile profile = store.getSpecificProfile(profileSwitch.profileName); if (profile != null) { profileSwitch.profileJson = profile.getData().toString(); @@ -1742,4 +1518,4 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } // ---------------- Food handling --------------- -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/db/ExtendedBolus.java b/app/src/main/java/info/nightscout/androidaps/db/ExtendedBolus.java index 4232a2fc86..418de86c6c 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/ExtendedBolus.java +++ b/app/src/main/java/info/nightscout/androidaps/db/ExtendedBolus.java @@ -9,24 +9,25 @@ import android.graphics.Color; import com.j256.ormlite.field.DatabaseField; import com.j256.ormlite.table.DatabaseTable; +import org.json.JSONException; +import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Date; import java.util.Objects; import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.data.Iob; import info.nightscout.androidaps.data.IobTotal; -import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.interfaces.InsulinInterface; import info.nightscout.androidaps.interfaces.Interval; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Overview.graphExtensions.DataPointWithLabelInterface; import info.nightscout.androidaps.plugins.Overview.graphExtensions.PointsWithLabelGraphSeries; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.JsonHelper; import info.nightscout.utils.Round; /** @@ -91,6 +92,16 @@ public class ExtendedBolus implements Interval, DataPointWithLabelInterface { pumpId = t.pumpId; } + public static ExtendedBolus createFromJson(JSONObject json) { + ExtendedBolus extendedBolus = new ExtendedBolus(); + extendedBolus.source = Source.NIGHTSCOUT; + extendedBolus.date = JsonHelper.safeGetLong(json, "mills"); + extendedBolus.durationInMinutes = JsonHelper.safeGetInt(json, "duration"); + extendedBolus.insulin = JsonHelper.safeGetDouble(json, "relative") / 60 * extendedBolus.durationInMinutes; + extendedBolus._id = JsonHelper.safeGetString(json, "_id"); + extendedBolus.pumpId = JsonHelper.safeGetLong(json, "pumpId"); + return extendedBolus; + } // -------- Interval interface --------- Long cuttedEnd = null; @@ -285,4 +296,9 @@ public class ExtendedBolus implements Interval, DataPointWithLabelInterface { public int getColor() { return Color.CYAN; } + + @Override + public int getSecondColor() { + return 0; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/db/FoodHelper.java b/app/src/main/java/info/nightscout/androidaps/db/FoodHelper.java deleted file mode 100644 index a933c2166e..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/db/FoodHelper.java +++ /dev/null @@ -1,208 +0,0 @@ -package info.nightscout.androidaps.db; - -import com.j256.ormlite.android.AndroidConnectionSource; -import com.j256.ormlite.dao.Dao; -import com.j256.ormlite.stmt.PreparedQuery; -import com.j256.ormlite.stmt.QueryBuilder; -import com.j256.ormlite.stmt.Where; -import com.j256.ormlite.table.TableUtils; - -import org.json.JSONException; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.events.EventFoodDatabaseChanged; - -/** - * Created by mike on 24.09.2017. - */ - -public class FoodHelper { - private static Logger log = LoggerFactory.getLogger(FoodHelper.class); - - DatabaseHelper databaseHelper; - - private static final ScheduledExecutorService foodEventWorker = Executors.newSingleThreadScheduledExecutor(); - private static ScheduledFuture scheduledFoodEventPost = null; - - public FoodHelper(DatabaseHelper databaseHelper) { - this.databaseHelper = databaseHelper; - } - - private Dao getDaoFood() throws SQLException { - return databaseHelper.getDao(Food.class); - } - - public void resetFood() { - try { - TableUtils.dropTable(databaseHelper.getConnectionSource(), Food.class, true); - TableUtils.createTableIfNotExists(databaseHelper.getConnectionSource(), Food.class); - } catch (SQLException e) { - log.error("Unhandled exception", e); - } - scheduleFoodChange(); - } - - public List getFoodData() { - try { - Dao daoFood = getDaoFood(); - List foods; - QueryBuilder queryBuilder = daoFood.queryBuilder(); - PreparedQuery preparedQuery = queryBuilder.prepare(); - foods = daoFood.query(preparedQuery); - return foods; - } catch (SQLException e) { - log.error("Unhandled exception", e); - } - return new ArrayList<>(); - } - - public boolean createOrUpdate(Food food) { - try { - // find by NS _id - if (food._id != null && !food._id.equals("")) { - Food old; - - QueryBuilder queryBuilder = getDaoFood().queryBuilder(); - Where where = queryBuilder.where(); - where.eq("_id", food._id); - PreparedQuery preparedQuery = queryBuilder.prepare(); - List found = getDaoFood().query(preparedQuery); - if (found.size() > 0) { - old = found.get(0); - if (!old.isEqual(food)) { - getDaoFood().delete(old); // need to delete/create because date may change too - old.copyFrom(food); - getDaoFood().create(old); - log.debug("FOOD: Updating record by _id: " + old.toString()); - scheduleFoodChange(); - return true; - } else { - return false; - } - } else { - getDaoFood().createOrUpdate(food); - log.debug("FOOD: New record: " + food.toString()); - scheduleFoodChange(); - return true; - } - } - } catch (SQLException e) { - log.error("Unhandled exception", e); - } - return false; - } - - public void delete(Food food) { - try { - getDaoFood().delete(food); - scheduleFoodChange(); - } catch (SQLException e) { - log.error("Unhandled exception", e); - } - } - - public static void scheduleFoodChange() { - class PostRunnable implements Runnable { - public void run() { - log.debug("Firing EventFoodChange"); - MainApp.bus().post(new EventFoodDatabaseChanged()); - scheduledFoodEventPost = null; - } - } - // prepare task for execution in 1 sec - // cancel waiting task to prevent sending multiple posts - if (scheduledFoodEventPost != null) - scheduledFoodEventPost.cancel(false); - Runnable task = new PostRunnable(); - final int sec = 1; - scheduledFoodEventPost = foodEventWorker.schedule(task, sec, TimeUnit.SECONDS); - - } - - /* - { - "_id": "551ee3ad368e06e80856e6a9", - "type": "food", - "category": "Zakladni", - "subcategory": "Napoje", - "name": "Mleko", - "portion": 250, - "carbs": 12, - "gi": 1, - "created_at": "2015-04-14T06:59:16.500Z", - "unit": "ml" - } - */ - public void createFoodFromJsonIfNotExists(JSONObject trJson) { - try { - Food food = new Food(); - if (trJson.has("type") && trJson.getString("type").equals("food")) { - if (trJson.has("_id")) - food._id = trJson.getString("_id"); - if (trJson.has("category")) - food.category = trJson.getString("category"); - if (trJson.has("subcategory")) - food.subcategory = trJson.getString("subcategory"); - if (trJson.has("name")) - food.name = trJson.getString("name"); - if (trJson.has("unit")) - food.units = trJson.getString("unit"); - if (trJson.has("portion")) - food.portion = trJson.getDouble("portion"); - if (trJson.has("carbs")) - food.carbs = trJson.getInt("carbs"); - if (trJson.has("gi")) - food.gi = trJson.getInt("gi"); - if (trJson.has("energy")) - food.energy = trJson.getInt("energy"); - if (trJson.has("protein")) - food.protein = trJson.getInt("protein"); - if (trJson.has("fat")) - food.fat = trJson.getInt("fat"); - } - createOrUpdate(food); - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - } - - public void deleteFoodById(String _id) { - Food stored = findFoodById(_id); - if (stored != null) { - log.debug("FOOD: Removing Food record from database: " + stored.toString()); - delete(stored); - scheduleFoodChange(); - } - } - - public Food findFoodById(String _id) { - try { - QueryBuilder queryBuilder = getDaoFood().queryBuilder(); - Where where = queryBuilder.where(); - where.eq("_id", _id); - PreparedQuery preparedQuery = queryBuilder.prepare(); - List list = getDaoFood().query(preparedQuery); - - if (list.size() == 1) { - return list.get(0); - } else { - return null; - } - } catch (SQLException e) { - log.error("Unhandled exception", e); - } - return null; - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/db/ICallback.java b/app/src/main/java/info/nightscout/androidaps/db/ICallback.java new file mode 100644 index 0000000000..7a9360035f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/db/ICallback.java @@ -0,0 +1,15 @@ +package info.nightscout.androidaps.db; + +import java.util.concurrent.ScheduledFuture; + +/** + * Created by triplem on 05.01.18. + */ + +public interface ICallback { + + void setPost(ScheduledFuture post); + + ScheduledFuture getPost(); + +} diff --git a/app/src/main/java/info/nightscout/androidaps/db/ProfileSwitch.java b/app/src/main/java/info/nightscout/androidaps/db/ProfileSwitch.java index dcce69639f..9dca91c595 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/ProfileSwitch.java +++ b/app/src/main/java/info/nightscout/androidaps/db/ProfileSwitch.java @@ -61,6 +61,31 @@ public class ProfileSwitch implements Interval, DataPointWithLabelInterface { private Profile profile = null; + public ProfileSwitch date(long date) { + this.date = date; + return this; + } + + public ProfileSwitch profileName(String profileName) { + this.profileName = profileName; + return this; + } + + public ProfileSwitch profile(Profile profile) { + this.profile = profile; + return this; + } + + public ProfileSwitch source(int source) { + this.source = source; + return this; + } + + public ProfileSwitch duration(int duration) { + this.durationInMinutes = duration; + return this; + } + @Nullable public Profile getProfileObject() { if (profile == null) @@ -238,6 +263,11 @@ public class ProfileSwitch implements Interval, DataPointWithLabelInterface { return Color.CYAN; } + @Override + public int getSecondColor() { + return 0; + } + public String toString() { return "ProfileSwitch{" + "date=" + date + diff --git a/app/src/main/java/info/nightscout/androidaps/db/TDD.java b/app/src/main/java/info/nightscout/androidaps/db/TDD.java new file mode 100644 index 0000000000..f34c79ff1a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/db/TDD.java @@ -0,0 +1,46 @@ +package info.nightscout.androidaps.db; + +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.table.DatabaseTable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Objects; + +/** + * Created by mike on 20.09.2017. + */ + + +@DatabaseTable(tableName = DatabaseHelper.DATABASE_TDDS) +public class TDD { + private static Logger log = LoggerFactory.getLogger(TDD.class); + + @DatabaseField(id = true) + public long date; + + @DatabaseField + public double bolus; + + @DatabaseField + public double basal; + + @DatabaseField + public double total; + + + public double getTotal(){ + return (total > 0d) ? total:(bolus+basal); + } + + + public TDD() { } + + public TDD(long date, double bolus, double basal, double total){ + this.date = date; + this.bolus = bolus; + this.basal = basal; + this.total = total; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/db/TempTarget.java b/app/src/main/java/info/nightscout/androidaps/db/TempTarget.java index c6d0ed18c4..ae016a0f29 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/TempTarget.java +++ b/app/src/main/java/info/nightscout/androidaps/db/TempTarget.java @@ -41,6 +41,10 @@ public class TempTarget implements Interval { @DatabaseField public int durationInMinutes; + public double target() { + return (low + high) / 2; + } + public boolean isEqual(TempTarget other) { if (date != other.date) { return false; @@ -67,6 +71,41 @@ public class TempTarget implements Interval { reason = t.reason; } + public TempTarget date(long date) { + this.date = date; + return this; + } + + public TempTarget low(double low) { + this.low = low; + return this; + } + + public TempTarget high(double high) { + this.high = high; + return this; + } + + public TempTarget duration(int duration) { + this.durationInMinutes = duration; + return this; + } + + public TempTarget reason(String reason) { + this.reason = reason; + return this; + } + + public TempTarget _id(String _id) { + this._id = _id; + return this; + } + + public TempTarget source(int source) { + this.source = source; + return this; + } + // -------- Interval interface --------- Long cuttedEnd = null; diff --git a/app/src/main/java/info/nightscout/androidaps/db/TemporaryBasal.java b/app/src/main/java/info/nightscout/androidaps/db/TemporaryBasal.java index 3feb9c4ad2..733027b188 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/TemporaryBasal.java +++ b/app/src/main/java/info/nightscout/androidaps/db/TemporaryBasal.java @@ -6,7 +6,6 @@ import com.j256.ormlite.table.DatabaseTable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Date; import java.util.Objects; import info.nightscout.androidaps.MainApp; @@ -17,6 +16,7 @@ import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.interfaces.InsulinInterface; import info.nightscout.androidaps.interfaces.Interval; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.SP; @@ -60,8 +60,36 @@ public class TemporaryBasal implements Interval { public TemporaryBasal() { } - public TemporaryBasal(long date) { + public TemporaryBasal date(long date) { this.date = date; + return this; + } + + public TemporaryBasal duration(int durationInMinutes) { + this.durationInMinutes = durationInMinutes; + return this; + } + + public TemporaryBasal absolute(double absoluteRate) { + this.absoluteRate = absoluteRate; + this.isAbsolute = true; + return this; + } + + public TemporaryBasal percent(int percentRate) { + this.percentRate = percentRate; + this.isAbsolute = false; + return this; + } + + public TemporaryBasal source(int source) { + this.source = source; + return this; + } + + public TemporaryBasal pumpId(long pumpId) { + this.pumpId = pumpId; + return this; } public TemporaryBasal(ExtendedBolus extendedBolus) { @@ -189,7 +217,7 @@ public class TemporaryBasal implements Interval { // -------- Interval interface end --------- - public IobTotal iobCalc(long time) { + public IobTotal iobCalc(long time, Profile profile) { if(isFakeExtended){ log.error("iobCalc should only be called on Extended boluses separately"); @@ -197,7 +225,6 @@ public class TemporaryBasal implements Interval { } IobTotal result = new IobTotal(time); - Profile profile = MainApp.getConfigBuilder().getProfile(time); InsulinInterface insulinInterface = ConfigBuilderPlugin.getActiveInsulin(); int realDuration = getDurationToTime(time); @@ -262,13 +289,13 @@ public class TemporaryBasal implements Interval { return (remainingMin < 0) ? 0 : Math.round(remainingMin); } - public double tempBasalConvertedToAbsolute(long time) { + public double tempBasalConvertedToAbsolute(long time, Profile profile) { if(isFakeExtended){ - return MainApp.getConfigBuilder().getProfile(time).getBasal(time) + netExtendedRate; + return profile.getBasal(time) + netExtendedRate; } else if (isAbsolute) { return absoluteRate; } else { - return MainApp.getConfigBuilder().getProfile(time).getBasal(time) * percentRate / 100; + return profile.getBasal(time) * percentRate / 100; } } @@ -325,13 +352,13 @@ public class TemporaryBasal implements Interval { if(profile != null) { double basal = profile.getBasal(); if(basal != 0){ - return Math.round(rate*100d/basal) + "% "; + return Math.round(rate*100d/basal) + "%"; } } } - return DecimalFormatter.to2Decimal(rate) + "U/h "; + return DecimalFormatter.to2Decimal(rate) + "U/h"; } else { // percent - return percentRate + "% "; + return percentRate + "%"; } } diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventCustomCalculationFinished.java b/app/src/main/java/info/nightscout/androidaps/events/EventCustomCalculationFinished.java new file mode 100644 index 0000000000..e52761dc58 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventCustomCalculationFinished.java @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.events; + +/** + * Created by mike on 13.02.2018. + */ + +public class EventCustomCalculationFinished extends Event { +} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventFeatureRunning.java b/app/src/main/java/info/nightscout/androidaps/events/EventFeatureRunning.java new file mode 100644 index 0000000000..0d07cd6c61 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventFeatureRunning.java @@ -0,0 +1,36 @@ +package info.nightscout.androidaps.events; + +/** + * Created by jamorham on 07/02/2018. + * + * Event to indicate that an app feature is being used, for example bolus wizard being opened + * + * The purpose this has been created for is to enable opportunistic connection to the pump + * so that it is already connected before the user wishes to enact a pump function + * + */ + +public class EventFeatureRunning extends Event { + + private Feature feature = Feature.UNKNOWN; + + public EventFeatureRunning() { + } + + public EventFeatureRunning(Feature feature) { + this.feature = feature; + } + + public Feature getFeature() { + return feature; + } + + public enum Feature { + UNKNOWN, + MAIN, + WIZARD, + + JUST_ADD_MORE_HERE + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventNetworkChange.java b/app/src/main/java/info/nightscout/androidaps/events/EventNetworkChange.java new file mode 100644 index 0000000000..ed639d643d --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventNetworkChange.java @@ -0,0 +1,10 @@ +package info.nightscout.androidaps.events; + +public class EventNetworkChange extends Event { + + public boolean mobileConnected = false; + public boolean wifiConnected = false; + + public String ssid; + public boolean roaming = false; +} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventNewBG.java b/app/src/main/java/info/nightscout/androidaps/events/EventNewBG.java index 2fb9919b00..dc4d434e0a 100644 --- a/app/src/main/java/info/nightscout/androidaps/events/EventNewBG.java +++ b/app/src/main/java/info/nightscout/androidaps/events/EventNewBG.java @@ -1,7 +1,17 @@ package info.nightscout.androidaps.events; +import android.support.annotation.Nullable; + +import info.nightscout.androidaps.db.BgReading; + /** * Created by mike on 05.06.2016. */ public class EventNewBG extends EventLoop { + @Nullable + public final BgReading bgReading; + + public EventNewBG(BgReading bgReading) { + this.bgReading = bgReading; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventNsFood.java b/app/src/main/java/info/nightscout/androidaps/events/EventNsFood.java new file mode 100644 index 0000000000..90b6f5681b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventNsFood.java @@ -0,0 +1,35 @@ +package info.nightscout.androidaps.events; + +import android.os.Bundle; + +/** + * Event which is published with data fetched from NightScout specific for the + * Food-class. + * + * Payload is the from NS retrieved JSON-String which should be handled by all + * subscriber. + */ + +public class EventNsFood extends Event { + + public static final int ADD = 0; + public static final int UPDATE = 1; + public static final int REMOVE = 2; + + private final int mode; + + private final Bundle payload; + + public EventNsFood(int mode, Bundle payload) { + this.mode = mode; + this.payload = payload; + } + + public int getMode() { + return mode; + } + + public Bundle getPayload() { + return payload; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventNsTreatment.java b/app/src/main/java/info/nightscout/androidaps/events/EventNsTreatment.java new file mode 100644 index 0000000000..2c5ba6c9c0 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventNsTreatment.java @@ -0,0 +1,36 @@ +package info.nightscout.androidaps.events; + +import org.json.JSONObject; + + +/** + * Event which is published with data fetched from NightScout specific for the + * Treatment-class. + *

+ * Payload is the from NS retrieved JSON-String which should be handled by all + * subscriber. + */ + +public class EventNsTreatment extends Event { + + public static final int ADD = 0; + public static final int UPDATE = 1; + public static final int REMOVE = 2; + + private final int mode; + + private final JSONObject payload; + + public EventNsTreatment(int mode, JSONObject payload) { + this.mode = mode; + this.payload = payload; + } + + public int getMode() { + return mode; + } + + public JSONObject getPayload() { + return payload; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentChange.java b/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentChange.java index 439d9a7124..989b24b7f9 100644 --- a/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentChange.java +++ b/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentChange.java @@ -1,7 +1,17 @@ package info.nightscout.androidaps.events; +import android.support.annotation.Nullable; + +import info.nightscout.androidaps.plugins.Treatments.Treatment; + /** * Created by mike on 04.06.2016. */ public class EventTreatmentChange extends EventLoop { + @Nullable + public final Treatment treatment; + + public EventTreatmentChange(Treatment treatment) { + this.treatment = treatment; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/APSInterface.java b/app/src/main/java/info/nightscout/androidaps/interfaces/APSInterface.java index 11c01112ba..0b965d48af 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/APSInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/APSInterface.java @@ -11,5 +11,5 @@ public interface APSInterface { public APSResult getLastAPSResult(); public Date getLastAPSRun(); - public void invoke(String initiator); + public void invoke(String initiator, boolean tempBasalFallback); } diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/BgSourceInterface.java b/app/src/main/java/info/nightscout/androidaps/interfaces/BgSourceInterface.java index 7ecc89ef66..a45ab083e7 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/BgSourceInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/BgSourceInterface.java @@ -4,4 +4,5 @@ package info.nightscout.androidaps.interfaces; * Created by mike on 20.06.2016. */ public interface BgSourceInterface { + boolean advancedFilteringSupported(); } diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/Constraint.java b/app/src/main/java/info/nightscout/androidaps/interfaces/Constraint.java new file mode 100644 index 0000000000..58e6045bd1 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/Constraint.java @@ -0,0 +1,117 @@ +package info.nightscout.androidaps.interfaces; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by mike on 19.03.2018. + */ + +public class Constraint { + private static Logger log = LoggerFactory.getLogger(Constraint.class); + + T value; + T originalValue; + + List reasons = new ArrayList<>(); + List mostLimiting = new ArrayList<>(); + + public Constraint(T value) { + this.value = value; + this.originalValue = value; + } + + public T value() { + return value; + } + + public T originalValue() { + return originalValue; + } + + public Constraint set(T value) { + this.value = value; + this.originalValue = value; + return this; + } + + public Constraint set(T value, String reason, Object from) { + this.value = value; + addReason(reason, from); + addMostLimingReason(reason, from); + return this; + } + + public Constraint setIfSmaller(T value, String reason, Object from) { + if (value.compareTo(this.value) < 0) { + this.value = value; + mostLimiting.clear(); + addMostLimingReason(reason, from); + } + if (value.compareTo(this.originalValue) < 0) { + addReason(reason, from); + } + return this; + } + + public Constraint setIfGreater(T value, String reason, Object from) { + if (value.compareTo(this.value) > 0) { + this.value = value; + mostLimiting.clear(); + addMostLimingReason(reason, from); + } + if (value.compareTo(this.originalValue) > 0) { + addReason(reason, from); + } + return this; + } + + public Constraint addReason(String reason, Object from) { + reasons.add(from.getClass().getSimpleName().replace("Plugin", "") + ": " + reason); + return this; + } + + public Constraint addMostLimingReason(String reason, Object from) { + mostLimiting.add(from.getClass().getSimpleName().replace("Plugin", "") + ": " + reason); + return this; + } + + public String getReasons() { + StringBuilder sb = new StringBuilder(); + int count = 0; + for (String r : reasons) { + if (count++ != 0) sb.append("\n"); + sb.append(r); + } + log.debug("Limiting origial value: " + originalValue + " to " + value + ". Reason: " + sb.toString()); + return sb.toString(); + } + + public List getReasonList() { + return reasons; + } + + public String getMostLimitedReasons() { + StringBuilder sb = new StringBuilder(); + int count = 0; + for (String r : mostLimiting) { + if (count++ != 0) sb.append("\n"); + sb.append(r); + } + log.debug("Limiting origial value: " + originalValue + " to " + value + ". Reason: " + sb.toString()); + return sb.toString(); + } + + public List getMostLimitedReasonList() { + return mostLimiting; + } + + public void copyReasons(Constraint another) { + for (String s: another.getReasonList()) { + reasons.add(s); + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/ConstraintsInterface.java b/app/src/main/java/info/nightscout/androidaps/interfaces/ConstraintsInterface.java index aac7918bdc..a1daa08a56 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/ConstraintsInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/ConstraintsInterface.java @@ -1,30 +1,54 @@ package info.nightscout.androidaps.interfaces; -import info.nightscout.androidaps.plugins.Loop.APSResult; +import info.nightscout.androidaps.data.Profile; /** * Created by mike on 15.06.2016. */ public interface ConstraintsInterface { - boolean isLoopEnabled(); + default Constraint isLoopInvokationAllowed(Constraint value) { + return value; + } - boolean isClosedModeEnabled(); + default Constraint isClosedLoopAllowed(Constraint value) { + return value; + } - boolean isAutosensModeEnabled(); + default Constraint isAutosensModeEnabled(Constraint value) { + return value; + } - boolean isAMAModeEnabled(); + default Constraint isAMAModeEnabled(Constraint value) { + return value; + } - boolean isSMBModeEnabled(); + default Constraint isSMBModeEnabled(Constraint value) { + return value; + } - Double applyBasalConstraints(Double absoluteRate); + default Constraint isAdvancedFilteringEnabled(Constraint value) { + return value; + } - Integer applyBasalConstraints(Integer percentRate); + default Constraint applyBasalConstraints(Constraint absoluteRate, Profile profile) { + return absoluteRate; + } - Double applyBolusConstraints(Double insulin); + default Constraint applyBasalPercentConstraints(Constraint percentRate, Profile profile) { + return percentRate; + } - Integer applyCarbsConstraints(Integer carbs); + default Constraint applyBolusConstraints(Constraint insulin) { + return insulin; + } - Double applyMaxIOBConstraints(Double maxIob); + default Constraint applyCarbsConstraints(Constraint carbs) { + return carbs; + } + + default Constraint applyMaxIOBConstraints(Constraint maxIob) { + return maxIob; + }; } diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/InsulinInterface.java b/app/src/main/java/info/nightscout/androidaps/interfaces/InsulinInterface.java index 48efa587d8..a123554ae8 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/InsulinInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/InsulinInterface.java @@ -1,25 +1,23 @@ package info.nightscout.androidaps.interfaces; -import java.util.Date; - import info.nightscout.androidaps.data.Iob; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.Treatments.Treatment; /** * Created by mike on 17.04.2017. */ public interface InsulinInterface { - final int FASTACTINGINSULIN = 0; - final int FASTACTINGINSULINPROLONGED = 1; - final int OREF_RAPID_ACTING = 2; - final int OREF_ULTRA_RAPID_ACTING = 3; - final int OREF_FREE_PEAK = 4; + int FASTACTINGINSULIN = 0; + int FASTACTINGINSULINPROLONGED = 1; + int OREF_RAPID_ACTING = 2; + int OREF_ULTRA_RAPID_ACTING = 3; + int OREF_FREE_PEAK = 4; int getId(); String getFriendlyName(); String getComment(); double getDia(); - public Iob iobCalcForTreatment(Treatment treatment, long time, double dia); + Iob iobCalcForTreatment(Treatment treatment, long time, double dia); } diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/PluginBase.java b/app/src/main/java/info/nightscout/androidaps/interfaces/PluginBase.java index 08db6210d4..1173936430 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/PluginBase.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/PluginBase.java @@ -1,34 +1,165 @@ package info.nightscout.androidaps.interfaces; -import java.util.Date; +import android.os.SystemClock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; /** * Created by mike on 09.06.2016. */ -public interface PluginBase { - int GENERAL = 1; - int TREATMENT = 2; - int SENSITIVITY = 3; - int PROFILE = 4; - int APS = 5; - int PUMP = 6; - int CONSTRAINTS = 7; - int LOOP = 8; - int BGSOURCE = 9; - int INSULIN = 10; - int LAST = 11; // keep always highest number +public abstract class PluginBase { + private static Logger log = LoggerFactory.getLogger(PluginBase.class); - int getType(); - String getFragmentClass(); + public enum State { + NOT_INITIALIZED, + ENABLED, + DISABLED + } - String getName(); - String getNameShort(); - boolean isEnabled(int type); - boolean isVisibleInTabs(int type); - boolean canBeHidden(int type); - boolean hasFragment(); - boolean showInList(int type); - void setFragmentEnabled(int type, boolean fragmentEnabled); - void setFragmentVisible(int type, boolean fragmentVisible); - int getPreferencesId(); + private State state = State.NOT_INITIALIZED; + private boolean isFragmentVisible = false; + public PluginDescription pluginDescription; + + + // Specific plugin with more Interfaces + protected boolean isProfileInterfaceEnabled = false; + + public PluginBase(PluginDescription pluginDescription) { + this.pluginDescription = pluginDescription; + } + +// public PluginType getType() { +// return mainType; +// } + +// public String getFragmentClass() { +// return fragmentClass; +// } + + public String getName() { + if (pluginDescription.pluginName == -1) + return "UKNOWN"; + else + return MainApp.gs(pluginDescription.pluginName); + } + + public String getNameShort() { + if (pluginDescription.shortName == -1) + return getName(); + String name = MainApp.gs(pluginDescription.shortName); + if (!name.trim().isEmpty()) //only if translation exists + return name; + // use long name as fallback + return getName(); + } + + public PluginType getType() { + return pluginDescription.mainType; + } + + public int getPreferencesId() { + return pluginDescription.preferencesId; + } + + public int getAdvancedPreferencesId() { + return pluginDescription.advancedPreferencesId; + } + + public boolean isEnabled(PluginType type) { + if (pluginDescription.alwaysEnabled && type == pluginDescription.mainType) + return true; + if (pluginDescription.mainType == PluginType.CONSTRAINTS && type == PluginType.CONSTRAINTS) + return true; + if (type == pluginDescription.mainType) + return state == State.ENABLED && specialEnableCondition(); + if (type == PluginType.CONSTRAINTS && pluginDescription.mainType == PluginType.PUMP && isEnabled(PluginType.PUMP)) + return true; + if (type == PluginType.PROFILE && pluginDescription.mainType == PluginType.PUMP) + return isProfileInterfaceEnabled; + return false; + } + + public boolean hasFragment() { + return pluginDescription.fragmentClass != null; + } + + + /** + * So far plugin can have it's main type + ConstraintInterface + ProfileInterface + * ConstraintInterface is enabled if main plugin is enabled + * ProfileInterface can be enabled only if main iterface is enable + */ + + public void setPluginEnabled(PluginType type, boolean newState) { + if (type == pluginDescription.mainType) { + if (newState == true) { // enabling plugin + if (state != State.ENABLED) { + onStateChange(type, state, State.ENABLED); + state = State.ENABLED; + log.debug("Starting: " + getName()); + onStart(); + } + } else { // disabling plugin + if (state == State.ENABLED) { + onStateChange(type, state, State.ENABLED); + state = State.DISABLED; + onStop(); + log.debug("Stopping: " + getName()); + } + } + } else if (type == PluginType.PROFILE) { + isProfileInterfaceEnabled = newState; + } + + } + + public void setFragmentVisible(PluginType type, boolean fragmentVisible) { + if (type == pluginDescription.mainType) { + isFragmentVisible = fragmentVisible && specialEnableCondition(); + } + } + + public boolean isFragmentVisible() { + if (pluginDescription.alwayVisible) + return true; + if (pluginDescription.neverVisible) + return false; + return isFragmentVisible; + } + + public boolean showInList(PluginType type) { + if (pluginDescription.mainType == type) + return pluginDescription.showInList && specialShowInListCondition(); + + if (type == PluginType.PROFILE && pluginDescription.mainType == PluginType.PUMP) + return isEnabled(PluginType.PUMP); + return false; + } + + public boolean specialEnableCondition() { + return true; + } + + public boolean specialShowInListCondition() { + return true; + } + + protected void onStart() { + if (getType() == PluginType.PUMP) { + new Thread(() -> { + SystemClock.sleep(3000); + ConfigBuilderPlugin.getCommandQueue().readStatus("Pump driver changed.", null); + }).start(); + } + } + + protected void onStop() { + } + + protected void onStateChange(PluginType type, State oldState, State newState) { + } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/PluginDescription.java b/app/src/main/java/info/nightscout/androidaps/interfaces/PluginDescription.java new file mode 100644 index 0000000000..c86ae639fa --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/PluginDescription.java @@ -0,0 +1,84 @@ +package info.nightscout.androidaps.interfaces; + +public class PluginDescription { + PluginType mainType = PluginType.GENERAL; + String fragmentClass = null; + public boolean alwayVisible = false; + public boolean neverVisible = false; + public boolean alwaysEnabled = false; + boolean showInList = true; + int pluginName = -1; + int shortName = -1; + int preferencesId = -1; + int advancedPreferencesId = -1; + public boolean enableByDefault = false; + public boolean visibleByDefault = false; + + public PluginDescription mainType(PluginType mainType) { + this.mainType = mainType; + return this; + } + + public PluginDescription fragmentClass(String fragmentClass) { + this.fragmentClass = fragmentClass; + return this; + } + + public PluginDescription alwaysEnabled(boolean alwaysEnabled) { + this.alwaysEnabled = alwaysEnabled; + return this; + } + + public PluginDescription alwayVisible(boolean alwayVisible) { + this.alwayVisible = alwayVisible; + return this; + } + + public PluginDescription neverVisible(boolean neverVisible) { + this.neverVisible = neverVisible; + return this; + } + + public PluginDescription showInList(boolean showInList) { + this.showInList = showInList; + return this; + } + + public PluginDescription pluginName(int pluginName) { + this.pluginName = pluginName; + return this; + } + + public PluginDescription shortName(int shortName) { + this.shortName = shortName; + return this; + } + + public PluginDescription preferencesId(int preferencesId) { + this.preferencesId = preferencesId; + return this; + } + + public PluginDescription advancedPreferencesId(int advancedPreferencesId) { + this.advancedPreferencesId = advancedPreferencesId; + return this; + } + + public PluginDescription enableByDefault(boolean enableByDefault) { + this.enableByDefault = enableByDefault; + return this; + } + + public PluginDescription visibleByDefault(boolean visibleByDefault) { + this.visibleByDefault = visibleByDefault; + return this; + } + + public String getFragmentClass() { + return fragmentClass; + } + + public PluginType getType() { + return mainType; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/PluginType.java b/app/src/main/java/info/nightscout/androidaps/interfaces/PluginType.java new file mode 100644 index 0000000000..1e5685f2ac --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/PluginType.java @@ -0,0 +1,14 @@ +package info.nightscout.androidaps.interfaces; + +public enum PluginType { + GENERAL, + TREATMENT, + SENSITIVITY, + PROFILE, + APS, + PUMP, + CONSTRAINTS, + LOOP, + BGSOURCE, + INSULIN +} diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/PumpDescription.java b/app/src/main/java/info/nightscout/androidaps/interfaces/PumpDescription.java index 10b0b3f453..f3d93b7c65 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/PumpDescription.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/PumpDescription.java @@ -27,6 +27,8 @@ public class PumpDescription { public double tempAbsoluteStep = 0.05d; public int tempDurationStep = 60; + public boolean tempDurationStep15mAllowed = false; + public boolean tempDurationStep30mAllowed = false; public int tempMaxDuration = 12 * 60; @@ -38,6 +40,11 @@ public class PumpDescription { public boolean storesCarbInfo = true; + public boolean is30minBasalRatesCapable = false; + + public boolean supportsTDDs = false; + public boolean needsManualTDDLoad = true; + public void resetSettings() { @@ -71,6 +78,4 @@ public class PumpDescription { storesCarbInfo = true; } - - } diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java b/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java index 087a1dd0bd..65911f82c6 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java @@ -5,8 +5,8 @@ import org.json.JSONObject; import java.util.Date; import info.nightscout.androidaps.data.DetailedBolusInfo; -import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.data.PumpEnactResult; /** * Created by mike on 04.06.2016. @@ -35,8 +35,8 @@ public interface PumpInterface { PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo); void stopBolusDelivering(); - PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean enforceNew); - PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, boolean enforceNew); + PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, Profile profile, boolean enforceNew); + PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, Profile profile, boolean enforceNew); PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes); //some pumps might set a very short temp close to 100% as cancelling a temp can be noisy //when the cancel request is requested by the user (forced), the pump should always do a real cancel @@ -44,7 +44,7 @@ public interface PumpInterface { PumpEnactResult cancelExtendedBolus(); // Status to be passed to NS - JSONObject getJSONStatus(); + JSONObject getJSONStatus(Profile profile, String profileName); String deviceID(); // Pump capabilities @@ -54,4 +54,7 @@ public interface PumpInterface { String shortStatus(boolean veryShort); boolean isFakingTempsByExtendedBoluses(); + + PumpEnactResult loadTDDs(); + } diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/TreatmentsInterface.java b/app/src/main/java/info/nightscout/androidaps/interfaces/TreatmentsInterface.java index 044b259ac9..27a6a0846b 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/TreatmentsInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/TreatmentsInterface.java @@ -5,11 +5,12 @@ import java.util.List; import info.nightscout.androidaps.data.DetailedBolusInfo; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.ProfileSwitch; import info.nightscout.androidaps.db.TempTarget; import info.nightscout.androidaps.db.TemporaryBasal; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.androidaps.data.Intervals; import info.nightscout.androidaps.data.ProfileIntervals; @@ -24,12 +25,13 @@ public interface TreatmentsInterface { IobTotal getLastCalculationTreatments(); IobTotal getCalculationToTimeTreatments(long time); IobTotal getLastCalculationTempBasals(); - IobTotal getCalculationToTimeTempBasals(long time); + IobTotal getCalculationToTimeTempBasals(long time, Profile profile); MealData getMealData(); List getTreatmentsFromHistory(); List getTreatments5MinBackFromHistory(long time); + long getLastBolusTime(); // real basals (not faked by extended bolus) boolean isInHistoryRealTempBasalInProgress(); @@ -40,8 +42,6 @@ public interface TreatmentsInterface { // basal that can be faked by extended boluses boolean isTempBasalInProgress(); TemporaryBasal getTempBasalFromHistory(long time); - double getTempBasalAbsoluteRateHistory(); - double getTempBasalRemainingMinutesFromHistory(); Intervals getTemporaryBasalsFromHistory(); boolean isInHistoryExtendedBoluslInProgress(); @@ -55,6 +55,7 @@ public interface TreatmentsInterface { TempTarget getTempTargetFromHistory(); TempTarget getTempTargetFromHistory(long time); Intervals getTempTargetsFromHistory(); + void addToHistoryTempTarget(TempTarget tempTarget); ProfileSwitch getProfileSwitchFromHistory(long time); ProfileIntervals getProfileSwitchesFromHistory(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Actions/ActionsFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Actions/ActionsFragment.java index d6f56e501b..cc73571b9d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Actions/ActionsFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Actions/ActionsFragment.java @@ -2,6 +2,7 @@ package info.nightscout.androidaps.plugins.Actions; import android.app.Activity; +import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; @@ -9,14 +10,14 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import com.crashlytics.android.Crashlytics; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import com.squareup.otto.Subscribe; import info.nightscout.androidaps.Config; +import info.nightscout.androidaps.HistoryBrowseActivity; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.TDDStatsActivity; import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.events.EventExtendedBolusChange; @@ -32,6 +33,8 @@ import info.nightscout.androidaps.plugins.Careportal.Dialogs.NewNSTreatmentDialo import info.nightscout.androidaps.plugins.Careportal.OptionsToShow; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.SingleClickButton; /** @@ -52,6 +55,8 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL SingleClickButton tempBasal; SingleClickButton tempBasalCancel; SingleClickButton fill; + SingleClickButton tddStats; + SingleClickButton history; public ActionsFragment() { super(); @@ -71,6 +76,8 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL tempBasal = (SingleClickButton) view.findViewById(R.id.actions_settempbasal); tempBasalCancel = (SingleClickButton) view.findViewById(R.id.actions_canceltempbasal); fill = (SingleClickButton) view.findViewById(R.id.actions_fill); + tddStats = view.findViewById(R.id.actions_tddstats); + history = view.findViewById(R.id.actions_historybrowser); profileSwitch.setOnClickListener(this); tempTarget.setOnClickListener(this); @@ -79,11 +86,13 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL tempBasal.setOnClickListener(this); tempBasalCancel.setOnClickListener(this); fill.setOnClickListener(this); + history.setOnClickListener(this); + tddStats.setOnClickListener(this); updateGUI(); return view; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } return null; @@ -116,9 +125,14 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL activity.runOnUiThread(new Runnable() { @Override public void run() { - if (MainApp.getConfigBuilder().getActiveProfileInterface().getProfile() == null) { - tempTarget.setVisibility(View.GONE); + if (MainApp.getConfigBuilder().getActiveProfileInterface().getProfile() != null) { + profileSwitch.setVisibility(View.VISIBLE); + } else { profileSwitch.setVisibility(View.GONE); + } + + if (MainApp.getConfigBuilder().getProfile() == null) { + tempTarget.setVisibility(View.GONE); extendedBolus.setVisibility(View.GONE); extendedBolusCancel.setVisibility(View.GONE); tempBasal.setVisibility(View.GONE); @@ -126,22 +140,25 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL fill.setVisibility(View.GONE); return; } + final PumpInterface pump = ConfigBuilderPlugin.getActivePump(); - if (!pump.getPumpDescription().isSetBasalProfileCapable || !pump.isInitialized() || pump.isSuspended()) + final boolean basalprofileEnabled = MainApp.isEngineeringModeOrRelease() + && pump.getPumpDescription().isSetBasalProfileCapable; + + if (!basalprofileEnabled || !pump.isInitialized() || pump.isSuspended()) profileSwitch.setVisibility(View.GONE); else profileSwitch.setVisibility(View.VISIBLE); - if (!pump.getPumpDescription().isExtendedBolusCapable || !pump.isInitialized() || pump.isSuspended() || pump.isFakingTempsByExtendedBoluses()) { extendedBolus.setVisibility(View.GONE); extendedBolusCancel.setVisibility(View.GONE); } else { - if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) { + ExtendedBolus activeExtendedBolus = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(System.currentTimeMillis()); + if (activeExtendedBolus != null) { extendedBolus.setVisibility(View.GONE); extendedBolusCancel.setVisibility(View.VISIBLE); - ExtendedBolus running = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); - extendedBolusCancel.setText(MainApp.instance().getString(R.string.cancel) + " " + running.toString()); + extendedBolusCancel.setText(MainApp.instance().getString(R.string.cancel) + " " + activeExtendedBolus.toString()); } else { extendedBolus.setVisibility(View.VISIBLE); extendedBolusCancel.setVisibility(View.GONE); @@ -153,10 +170,10 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL tempBasal.setVisibility(View.GONE); tempBasalCancel.setVisibility(View.GONE); } else { - if (MainApp.getConfigBuilder().isTempBasalInProgress()) { + final TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); + if (activeTemp != null) { tempBasal.setVisibility(View.GONE); tempBasalCancel.setVisibility(View.VISIBLE); - final TemporaryBasal activeTemp = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); tempBasalCancel.setText(MainApp.instance().getString(R.string.cancel) + " " + activeTemp.toStringShort()); } else { tempBasal.setVisibility(View.VISIBLE); @@ -173,6 +190,9 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL tempTarget.setVisibility(View.GONE); else tempTarget.setVisibility(View.VISIBLE); + + if (!ConfigBuilderPlugin.getActivePump().getPumpDescription().supportsTDDs) tddStats.setVisibility(View.GONE); + else tddStats.setVisibility(View.VISIBLE); } }); } @@ -201,15 +221,15 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL newExtendedDialog.show(manager, "NewExtendedDialog"); break; case R.id.actions_extendedbolus_cancel: - if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) { + if (TreatmentsPlugin.getPlugin().isInHistoryExtendedBoluslInProgress()) { ConfigBuilderPlugin.getCommandQueue().cancelExtended(null); - Answers.getInstance().logCustom(new CustomEvent("CancelExtended")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("CancelExtended")); } break; case R.id.actions_canceltempbasal: - if (MainApp.getConfigBuilder().isTempBasalInProgress()) { + if (TreatmentsPlugin.getPlugin().isTempBasalInProgress()) { ConfigBuilderPlugin.getCommandQueue().cancelTempBasal(true, null); - Answers.getInstance().logCustom(new CustomEvent("CancelTemp")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("CancelTemp")); } break; case R.id.actions_settempbasal: @@ -220,6 +240,12 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL FillDialog fillDialog = new FillDialog(); fillDialog.show(manager, "FillDialog"); break; + case R.id.actions_historybrowser: + startActivity(new Intent(getContext(), HistoryBrowseActivity.class)); + break; + case R.id.actions_tddstats: + startActivity(new Intent(getContext(), TDDStatsActivity.class)); + break; } } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Actions/ActionsPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Actions/ActionsPlugin.java index 3c71626e64..2397410693 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Actions/ActionsPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Actions/ActionsPlugin.java @@ -1,82 +1,22 @@ package info.nightscout.androidaps.plugins.Actions; -import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; /** * Created by mike on 05.11.2016. */ -public class ActionsPlugin implements PluginBase { +public class ActionsPlugin extends PluginBase { - private boolean fragmentEnabled = true; - private boolean fragmentVisible = true; - - @Override - public int getType() { - return PluginBase.GENERAL; + public ActionsPlugin() { + super(new PluginDescription() + .mainType(PluginType.GENERAL) + .fragmentClass(ActionsFragment.class.getName()) + .pluginName(R.string.actions) + .shortName(R.string.actions_shortname) + ); } - - @Override - public String getFragmentClass() { - return ActionsFragment.class.getName(); - } - - @Override - public String getName() { - return MainApp.sResources.getString(R.string.actions); - } - - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.actions_shortname); - if (!name.trim().isEmpty()){ - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == GENERAL && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == GENERAL && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == GENERAL) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == GENERAL) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return -1; - } - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Actions/dialogs/FillDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/Actions/dialogs/FillDialog.java index 23fa39066f..5e99a6ca17 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Actions/dialogs/FillDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Actions/dialogs/FillDialog.java @@ -1,11 +1,13 @@ package info.nightscout.androidaps.plugins.Actions.dialogs; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v7.app.AlertDialog; +import android.text.Editable; +import android.text.Html; +import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; @@ -13,40 +15,73 @@ import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.LinearLayout; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; +import com.google.common.base.Joiner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.text.DecimalFormat; +import java.util.LinkedList; +import java.util.List; -import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.db.CareportalEvent; import info.nightscout.androidaps.db.Source; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Overview.Dialogs.ErrorHelperActivity; import info.nightscout.androidaps.queue.Callback; import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.FabricPrivacy; +import info.nightscout.utils.NSUpload; import info.nightscout.utils.NumberPicker; import info.nightscout.utils.SP; import info.nightscout.utils.SafeParse; +import info.nightscout.utils.ToastUtils; + +import static info.nightscout.utils.DateUtil.now; public class FillDialog extends DialogFragment implements OnClickListener { private static Logger log = LoggerFactory.getLogger(FillDialog.class); - Button deliverButton; + private CheckBox pumpSiteChangeCheckbox; + private CheckBox insulinCartridgeChangeCheckbox; + + private NumberPicker editInsulin; double amount1 = 0d; double amount2 = 0d; double amount3 = 0d; - NumberPicker editInsulin; + private EditText notesEdit; - public FillDialog() { + final private TextWatcher textWatcher = new TextWatcher() { + @Override + public void afterTextChanged(Editable s) { + validateInputs(); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + }; + + private void validateInputs() { + int time = editInsulin.getValue().intValue(); + if (Math.abs(time) > 12 * 60) { + editInsulin.setValue(0d); + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.constraintapllied)); + } } @Override @@ -54,110 +89,122 @@ public class FillDialog extends DialogFragment implements OnClickListener { Bundle savedInstanceState) { View view = inflater.inflate(R.layout.actions_fill_dialog, null, false); - deliverButton = (Button) view.findViewById(R.id.treatments_newtreatment_deliverbutton); + view.findViewById(R.id.ok).setOnClickListener(this); + view.findViewById(R.id.cancel).setOnClickListener(this); - deliverButton.setOnClickListener(this); getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE); getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); - Double maxInsulin = MainApp.getConfigBuilder().applyBolusConstraints(Constants.bolusOnlyForCheckLimit); + pumpSiteChangeCheckbox = view.findViewById(R.id.fill_catheter_change); + insulinCartridgeChangeCheckbox = view.findViewById(R.id.fill_cartridge_change); + + Double maxInsulin = MainApp.getConstraintChecker().getMaxBolusAllowed().value(); double bolusstep = ConfigBuilderPlugin.getActivePump().getPumpDescription().bolusStep; - editInsulin = (NumberPicker) view.findViewById(R.id.treatments_newtreatment_insulinamount); - editInsulin.setParams(0d, 0d, maxInsulin, bolusstep, new DecimalFormat("0.00"), false); + editInsulin = view.findViewById(R.id.fill_insulinamount); + editInsulin.setParams(0d, 0d, maxInsulin, bolusstep, DecimalFormatter.pumpSupportedBolusFormat(), false, textWatcher); - //setup preset buttons - Button button1 = (Button) view.findViewById(R.id.fill_preset_button1); - Button button2 = (Button) view.findViewById(R.id.fill_preset_button2); - Button button3 = (Button) view.findViewById(R.id.fill_preset_button3); - View divider = view.findViewById(R.id.fill_preset_divider); + Button preset1Button = view.findViewById(R.id.fill_preset_button1); amount1 = SP.getDouble("fill_button1", 0.3); - amount2 = SP.getDouble("fill_button2", 0d); - amount3 = SP.getDouble("fill_button3", 0d); - if (amount1 > 0) { - button1.setVisibility(View.VISIBLE); - button1.setText(DecimalFormatter.to2Decimal(amount1) + "U"); - button1.setOnClickListener(this); + preset1Button.setVisibility(View.VISIBLE); + preset1Button.setText(DecimalFormatter.toPumpSupportedBolus(amount1)); // + "U"); + preset1Button.setOnClickListener(this); } else { - button1.setVisibility(View.GONE); + preset1Button.setVisibility(View.GONE); } + Button preset2Button = view.findViewById(R.id.fill_preset_button2); + amount2 = SP.getDouble("fill_button2", 0d); if (amount2 > 0) { - button2.setVisibility(View.VISIBLE); - button2.setText(DecimalFormatter.to2Decimal(amount2) + "U"); - button2.setOnClickListener(this); + preset2Button.setVisibility(View.VISIBLE); + preset2Button.setText(DecimalFormatter.toPumpSupportedBolus(amount2)); // + "U"); + preset2Button.setOnClickListener(this); } else { - button2.setVisibility(View.GONE); + preset2Button.setVisibility(View.GONE); } + Button preset3Button = view.findViewById(R.id.fill_preset_button3); + amount3 = SP.getDouble("fill_button3", 0d); if (amount3 > 0) { - button3.setVisibility(View.VISIBLE); - button3.setText(DecimalFormatter.to2Decimal(amount3) + "U"); - button3.setOnClickListener(this); + preset3Button.setVisibility(View.VISIBLE); + preset3Button.setText(DecimalFormatter.toPumpSupportedBolus(amount3)); // + "U"); + preset3Button.setOnClickListener(this); } else { - button3.setVisibility(View.GONE); + preset3Button.setVisibility(View.GONE); } - if (button1.getVisibility() == View.GONE && button2.getVisibility() == View.GONE && button3.getVisibility() == View.GONE) { - divider.setVisibility(View.GONE); - } + LinearLayout notesLayout = view.findViewById(R.id.fill_notes_layout); + notesLayout.setVisibility(SP.getBoolean(R.string.key_show_notes_entry_dialogs, false) ? View.VISIBLE : View.GONE); + notesEdit = view.findViewById(R.id.fill_notes); setCancelable(true); getDialog().setCanceledOnTouchOutside(false); return view; } - @Override - public void onResume() { - super.onResume(); - if (getDialog() != null) - getDialog().getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - } - @Override public void onClick(View view) { switch (view.getId()) { - case R.id.treatments_newtreatment_deliverbutton: - Double insulin = SafeParse.stringToDouble(editInsulin.getText().toString()); - confirmAndDeliver(insulin); + case R.id.ok: + confirmAndDeliver(); + break; + case R.id.cancel: + dismiss(); break; case R.id.fill_preset_button1: - confirmAndDeliver(amount1); + editInsulin.setValue(amount1); break; case R.id.fill_preset_button2: - confirmAndDeliver(amount2); + editInsulin.setValue(amount2); break; case R.id.fill_preset_button3: - confirmAndDeliver(amount3); + editInsulin.setValue(amount3); break; } } - private void confirmAndDeliver(Double insulin) { + private void confirmAndDeliver() { try { + Double insulin = SafeParse.stringToDouble(editInsulin.getText()); - String confirmMessage = getString(R.string.fillwarning) + "\n"; + List confirmMessage = new LinkedList<>(); - Double insulinAfterConstraints = MainApp.getConfigBuilder().applyBolusConstraints(insulin); - confirmMessage += getString(R.string.bolus) + ": " + insulinAfterConstraints + "U"; - if (insulinAfterConstraints - insulin != 0) - confirmMessage += "\n" + getString(R.string.constraintapllied); + Double insulinAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(insulin)).value(); + if (insulinAfterConstraints > 0) { + confirmMessage.add(MainApp.gs(R.string.fillwarning)); + confirmMessage.add(""); + confirmMessage.add(MainApp.gs(R.string.bolus) + ": " + "" + insulinAfterConstraints + "U" + ""); + if (!insulinAfterConstraints.equals(insulin)) + confirmMessage.add("" + MainApp.gs(R.string.bolusconstraintapplied) + ""); + } + + if (pumpSiteChangeCheckbox.isChecked()) + confirmMessage.add("" + "" + getString(R.string.record_pump_site_change) + ""); + + if (insulinCartridgeChangeCheckbox.isChecked()) + confirmMessage.add("" + "" + getString(R.string.record_insulin_cartridge_change) + ""); + + final String notes = notesEdit.getText().toString(); + if (!notes.isEmpty()) { + confirmMessage.add(MainApp.gs(R.string.careportal_newnstreatment_notes_label) + ": " + notes); + } final Double finalInsulinAfterConstraints = insulinAfterConstraints; final Context context = getContext(); AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(this.getContext().getString(R.string.confirmation)); - builder.setMessage(confirmMessage); - builder.setPositiveButton(getString(R.string.primefill), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { + builder.setTitle(MainApp.gs(R.string.confirmation)); + if (insulinAfterConstraints > 0 || pumpSiteChangeCheckbox.isChecked() || insulinCartridgeChangeCheckbox.isChecked()) { + builder.setMessage(Html.fromHtml(Joiner.on("
").join(confirmMessage))); + builder.setPositiveButton(getString(R.string.primefill), (dialog, id) -> { if (finalInsulinAfterConstraints > 0) { DetailedBolusInfo detailedBolusInfo = new DetailedBolusInfo(); detailedBolusInfo.insulin = finalInsulinAfterConstraints; detailedBolusInfo.context = context; detailedBolusInfo.source = Source.USER; detailedBolusInfo.isValid = false; // do not count it in IOB (for pump history) + detailedBolusInfo.notes = notes; ConfigBuilderPlugin.getCommandQueue().bolus(detailedBolusInfo, new Callback() { @Override public void run() { @@ -171,10 +218,16 @@ public class FillDialog extends DialogFragment implements OnClickListener { } } }); - Answers.getInstance().logCustom(new CustomEvent("Fill")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("Fill")); } - } - }); + if (pumpSiteChangeCheckbox.isChecked()) + NSUpload.uploadEvent(CareportalEvent.SITECHANGE, now(), notes); + if (insulinCartridgeChangeCheckbox.isChecked()) + NSUpload.uploadEvent(CareportalEvent.INSULINCHANGE, now() + 1000, notes); + }); + } else { + builder.setMessage(MainApp.gs(R.string.no_action_selected)); + } builder.setNegativeButton(getString(R.string.cancel), null); builder.show(); dismiss(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Actions/dialogs/NewExtendedBolusDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/Actions/dialogs/NewExtendedBolusDialog.java index dc6a356f1d..04dd783b04 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Actions/dialogs/NewExtendedBolusDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Actions/dialogs/NewExtendedBolusDialog.java @@ -10,7 +10,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import org.slf4j.Logger; @@ -18,12 +17,13 @@ import org.slf4j.LoggerFactory; import java.text.DecimalFormat; -import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Overview.Dialogs.ErrorHelperActivity; import info.nightscout.androidaps.queue.Callback; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.NumberPicker; import info.nightscout.utils.SafeParse; @@ -43,7 +43,7 @@ public class NewExtendedBolusDialog extends DialogFragment implements View.OnCli View view = inflater.inflate(R.layout.overview_newextendedbolus_dialog, container, false); - Double maxInsulin = MainApp.getConfigBuilder().applyBolusConstraints(Constants.bolusOnlyForCheckLimit); + Double maxInsulin = MainApp.getConstraintChecker().getMaxBolusAllowed().value(); editInsulin = (NumberPicker) view.findViewById(R.id.overview_newextendedbolus_insulin); editInsulin.setParams(0d, 0d, maxInsulin, 0.1d, new DecimalFormat("0.00"), false); @@ -70,7 +70,7 @@ public class NewExtendedBolusDialog extends DialogFragment implements View.OnCli String confirmMessage = getString(R.string.setextendedbolusquestion); - Double insulinAfterConstraint = MainApp.getConfigBuilder().applyBolusConstraints(insulin); + Double insulinAfterConstraint = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(insulin)).value(); confirmMessage += " " + insulinAfterConstraint + " U "; confirmMessage += getString(R.string.duration) + " " + durationInMinutes + "min ?"; if (insulinAfterConstraint - insulin != 0d) @@ -99,7 +99,7 @@ public class NewExtendedBolusDialog extends DialogFragment implements View.OnCli } } }); - Answers.getInstance().logCustom(new CustomEvent("ExtendedBolus")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("ExtendedBolus")); } }); builder.setNegativeButton(getString(R.string.cancel), null); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Actions/dialogs/NewTempBasalDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/Actions/dialogs/NewTempBasalDialog.java index ce3d45fc71..fb00879ae4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Actions/dialogs/NewTempBasalDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Actions/dialogs/NewTempBasalDialog.java @@ -1,6 +1,5 @@ package info.nightscout.androidaps.plugins.Actions.dialogs; -import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; @@ -13,7 +12,6 @@ import android.widget.LinearLayout; import android.widget.RadioButton; import android.widget.RadioGroup; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import org.slf4j.Logger; @@ -24,10 +22,12 @@ import java.text.DecimalFormat; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.PumpDescription; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Overview.Dialogs.ErrorHelperActivity; import info.nightscout.androidaps.queue.Callback; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.NumberPicker; import info.nightscout.utils.SafeParse; @@ -118,17 +118,21 @@ public class NewTempBasalDialog extends DialogFragment implements View.OnClickLi final boolean setAsPercent = percentRadio.isChecked(); int durationInMinutes = SafeParse.stringToInt(duration.getText()); + Profile profile = MainApp.getConfigBuilder().getProfile(); + if (profile == null) + return; + String confirmMessage = getString(R.string.setbasalquestion); if (setAsPercent) { int basalPercentInput = SafeParse.stringToInt(basalPercent.getText()); - percent = MainApp.getConfigBuilder().applyBasalConstraints(basalPercentInput); + percent = MainApp.getConstraintChecker().applyBasalPercentConstraints(new Constraint<>(basalPercentInput), profile).value(); confirmMessage += "\n" + percent + "% "; confirmMessage += "\n" + getString(R.string.duration) + " " + durationInMinutes + "min ?"; if (percent != basalPercentInput) confirmMessage += "\n" + getString(R.string.constraintapllied); } else { Double basalAbsoluteInput = SafeParse.stringToDouble(basalAbsolute.getText()); - absolute = MainApp.getConfigBuilder().applyBasalConstraints(basalAbsoluteInput); + absolute = MainApp.getConstraintChecker().applyBasalConstraints(new Constraint<>(basalAbsoluteInput), profile).value(); confirmMessage += "\n" + absolute + " U/h "; confirmMessage += "\n" + getString(R.string.duration) + " " + durationInMinutes + "min ?"; if (absolute - basalAbsoluteInput != 0d) @@ -158,11 +162,11 @@ public class NewTempBasalDialog extends DialogFragment implements View.OnClickLi } }; if (setAsPercent) { - ConfigBuilderPlugin.getCommandQueue().tempBasalPercent(finalBasalPercent, finalDurationInMinutes, true, callback); + ConfigBuilderPlugin.getCommandQueue().tempBasalPercent(finalBasalPercent, finalDurationInMinutes, true, profile, callback); } else { - ConfigBuilderPlugin.getCommandQueue().tempBasalAbsolute(finalBasal, finalDurationInMinutes, true, callback); + ConfigBuilderPlugin.getCommandQueue().tempBasalAbsolute(finalBasal, finalDurationInMinutes, true, profile, callback); } - Answers.getInstance().logCustom(new CustomEvent("TempBasal")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("TempBasal")); } }); builder.setNegativeButton(getString(R.string.cancel), null); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Careportal/CareportalFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Careportal/CareportalFragment.java index 4070bc880f..4719ed525c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Careportal/CareportalFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Careportal/CareportalFragment.java @@ -2,6 +2,7 @@ package info.nightscout.androidaps.plugins.Careportal; import android.app.Activity; +import android.graphics.Color; import android.os.Bundle; import android.support.v4.app.FragmentManager; import android.view.LayoutInflater; @@ -10,10 +11,11 @@ import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; import com.squareup.otto.Subscribe; -import info.nightscout.androidaps.BuildConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; @@ -22,10 +24,12 @@ import info.nightscout.androidaps.db.CareportalEvent; import info.nightscout.androidaps.events.EventCareportalEventChange; import info.nightscout.androidaps.plugins.Careportal.Dialogs.NewNSTreatmentDialog; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.NSClientInternal.data.NSSettingsStatus; import info.nightscout.androidaps.plugins.Overview.OverviewFragment; +import info.nightscout.utils.FabricPrivacy; public class CareportalFragment extends SubscriberFragment implements View.OnClickListener { + private static Logger log = LoggerFactory.getLogger(CareportalFragment.class); TextView iage; TextView cage; @@ -96,7 +100,7 @@ public class CareportalFragment extends SubscriberFragment implements View.OnCli noProfileView = view.findViewById(R.id.profileview_noprofile); butonsLayout = (LinearLayout) view.findViewById(R.id.careportal_buttons); - ProfileStore profileStore = ConfigBuilderPlugin.getActiveProfileInterface().getProfile(); + ProfileStore profileStore = MainApp.getConfigBuilder().getActiveProfileInterface().getProfile(); if (profileStore == null) { noProfileView.setVisibility(View.VISIBLE); butonsLayout.setVisibility(View.GONE); @@ -111,7 +115,7 @@ public class CareportalFragment extends SubscriberFragment implements View.OnCli updateGUI(); return view; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } return null; @@ -208,26 +212,54 @@ public class CareportalFragment extends SubscriberFragment implements View.OnCli public static void updateAge(Activity activity, final TextView sage, final TextView iage, final TextView cage, final TextView pbage) { if (activity != null) { activity.runOnUiThread( - new Runnable() { - @Override - public void run() { - CareportalEvent careportalEvent; - String notavailable = OverviewFragment.shorttextmode ? "-" : MainApp.sResources.getString(R.string.notavailable); - if (sage != null) { - careportalEvent = MainApp.getDbHelper().getLastCareportalEvent(CareportalEvent.SENSORCHANGE); - sage.setText(careportalEvent != null ? careportalEvent.age() : notavailable); + () -> { + CareportalEvent careportalEvent; + NSSettingsStatus nsSettings = new NSSettingsStatus().getInstance(); + + double iageUrgent = nsSettings.getExtendedWarnValue("iage", "urgent", 96); + double iageWarn = nsSettings.getExtendedWarnValue("iage", "warn", 72); + double cageUrgent = nsSettings.getExtendedWarnValue("cage", "urgent", 72); + double cageWarn = nsSettings.getExtendedWarnValue("cage", "warn", 48); + double sageUrgent = nsSettings.getExtendedWarnValue("sage", "urgent", 166); + double sageWarn = nsSettings.getExtendedWarnValue("sage", "warn", 164); + double pbageUrgent = nsSettings.getExtendedWarnValue("pgage", "urgent", 360); + double pbageWarn = nsSettings.getExtendedWarnValue("pgage", "warn", 240); + + String notavailable = OverviewFragment.shorttextmode ? "-" : MainApp.sResources.getString(R.string.notavailable); + if (sage != null) { + careportalEvent = MainApp.getDbHelper().getLastCareportalEvent(CareportalEvent.SENSORCHANGE); + if (careportalEvent != null) { + sage.setTextColor(CareportalFragment.determineTextColor(careportalEvent, sageWarn, sageUrgent)); + sage.setText(careportalEvent.age()); + } else { + sage.setText(notavailable); } - if (iage != null) { - careportalEvent = MainApp.getDbHelper().getLastCareportalEvent(CareportalEvent.INSULINCHANGE); - iage.setText(careportalEvent != null ? careportalEvent.age() : notavailable); + } + if (iage != null) { + careportalEvent = MainApp.getDbHelper().getLastCareportalEvent(CareportalEvent.INSULINCHANGE); + if (careportalEvent != null) { + iage.setTextColor(CareportalFragment.determineTextColor(careportalEvent, iageWarn, iageUrgent)); + iage.setText(careportalEvent.age()); + } else { + iage.setText(notavailable); } - if (cage != null) { - careportalEvent = MainApp.getDbHelper().getLastCareportalEvent(CareportalEvent.SITECHANGE); - cage.setText(careportalEvent != null ? careportalEvent.age() : notavailable); + } + if (cage != null) { + careportalEvent = MainApp.getDbHelper().getLastCareportalEvent(CareportalEvent.SITECHANGE); + if (careportalEvent != null) { + cage.setTextColor(CareportalFragment.determineTextColor(careportalEvent, cageWarn, cageUrgent)); + cage.setText(careportalEvent.age()); + } else { + cage.setText(notavailable); } - if (pbage != null) { - careportalEvent = MainApp.getDbHelper().getLastCareportalEvent(CareportalEvent.PUMPBATTERYCHANGE); - pbage.setText(careportalEvent != null ? careportalEvent.age() : notavailable); + } + if (pbage != null) { + careportalEvent = MainApp.getDbHelper().getLastCareportalEvent(CareportalEvent.PUMPBATTERYCHANGE); + if (careportalEvent != null) { + pbage.setTextColor(CareportalFragment.determineTextColor(careportalEvent, pbageWarn, pbageUrgent)); + pbage.setText(careportalEvent.age()); + } else { + pbage.setText(notavailable); } } } @@ -235,4 +267,15 @@ public class CareportalFragment extends SubscriberFragment implements View.OnCli } } + public static int determineTextColor(CareportalEvent careportalEvent, double warnThreshold, double urgentThreshold) { + if (careportalEvent.isOlderThan(urgentThreshold)) { + return MainApp.sResources.getColor(R.color.low); + } else if (careportalEvent.isOlderThan(warnThreshold)) { + return MainApp.sResources.getColor(R.color.high); + } else { + return Color.WHITE; + } + + } } + diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Careportal/CareportalPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Careportal/CareportalPlugin.java index a72fc200ea..1428880a81 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Careportal/CareportalPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Careportal/CareportalPlugin.java @@ -1,14 +1,12 @@ package info.nightscout.androidaps.plugins.Careportal; import info.nightscout.androidaps.Config; -import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; -public class CareportalPlugin implements PluginBase { - - private boolean fragmentEnabled = true; - private boolean fragmentVisible = true; +public class CareportalPlugin extends PluginBase { static CareportalPlugin careportalPlugin; @@ -19,70 +17,13 @@ public class CareportalPlugin implements PluginBase { return careportalPlugin; } - @Override - public int getType() { - return PluginBase.GENERAL; - } - - @Override - public String getFragmentClass() { - return CareportalFragment.class.getName(); - } - - @Override - public String getName() { - return MainApp.sResources.getString(R.string.careportal); - } - - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.careportal_shortname); - if (!name.trim().isEmpty()){ - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == GENERAL && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == GENERAL && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return !Config.NSCLIENT && !Config.G5UPLOADER; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == GENERAL) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == GENERAL) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return R.xml.pref_careportal; + public CareportalPlugin() { + super(new PluginDescription() + .mainType(PluginType.GENERAL) + .fragmentClass(CareportalFragment.class.getName()) + .pluginName(R.string.careportal) + .shortName(R.string.careportal_shortname) + ); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Careportal/Dialogs/NewNSTreatmentDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/Careportal/Dialogs/NewNSTreatmentDialog.java index 58d0603301..56aa0158cf 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Careportal/Dialogs/NewNSTreatmentDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Careportal/Dialogs/NewNSTreatmentDialog.java @@ -2,8 +2,6 @@ package info.nightscout.androidaps.plugins.Careportal.Dialogs; import android.app.Activity; -import android.content.DialogInterface; -import android.content.Intent; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v7.app.AlertDialog; @@ -16,15 +14,14 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; -import android.widget.CompoundButton; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.RadioButton; import android.widget.Spinner; import android.widget.TextView; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; +import com.google.common.collect.Lists; import com.wdullaer.materialdatetimepicker.date.DatePickerDialog; import com.wdullaer.materialdatetimepicker.time.RadialPickerLayout; import com.wdullaer.materialdatetimepicker.time.TimePickerDialog; @@ -38,6 +35,7 @@ import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; +import java.util.List; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; @@ -45,16 +43,18 @@ import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.GlucoseStatus; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.ProfileStore; +import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.CareportalEvent; import info.nightscout.androidaps.db.ProfileSwitch; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.db.TempTarget; -import info.nightscout.androidaps.events.EventNewBasalProfile; import info.nightscout.androidaps.plugins.Careportal.OptionsToShow; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.Overview.Dialogs.ErrorHelperActivity; -import info.nightscout.androidaps.queue.Callback; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.DateUtil; +import info.nightscout.utils.DefaultValueHelper; +import info.nightscout.utils.FabricPrivacy; +import info.nightscout.utils.HardLimits; +import info.nightscout.utils.JsonHelper; import info.nightscout.utils.NSUpload; import info.nightscout.utils.NumberPicker; import info.nightscout.utils.SP; @@ -70,8 +70,8 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick private static String event; Profile profile; - ProfileStore profileStore; - String units; + public ProfileStore profileStore; + String units = Constants.MGDL; TextView eventTypeText; LinearLayout layoutPercent; @@ -105,13 +105,19 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick Date eventTime; + private static Integer seconds = null; + public void setOptions(OptionsToShow options, int event) { this.options = options; - this.event = MainApp.sResources.getString(event); + this.event = MainApp.gs(event); } public NewNSTreatmentDialog() { super(); + + if (seconds == null) { + seconds = Double.valueOf(Math.random() * 59).intValue(); + } } @Override @@ -130,7 +136,7 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (options == null) return null; - getDialog().setTitle(getString(options.eventName)); + getDialog().setTitle(MainApp.gs(options.eventName)); setStyle(DialogFragment.STYLE_NORMAL, getTheme()); View view = inflater.inflate(R.layout.careportal_newnstreatment_dialog, container, false); @@ -166,52 +172,61 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick // profile profile = MainApp.getConfigBuilder().getProfile(); - profileStore = ConfigBuilderPlugin.getActiveProfileInterface().getProfile(); - ArrayList profileList; - units = profile != null ? profile.getUnits() : Constants.MGDL; - profileList = profileStore.getProfileList(); - ArrayAdapter adapter = new ArrayAdapter(getContext(), - R.layout.spinner_centered, profileList); - profileSpinner.setAdapter(adapter); - // set selected to actual profile - for (int p = 0; p < profileList.size(); p++) { - if (profileList.get(p).equals(MainApp.getConfigBuilder().getProfileName(false))) - profileSpinner.setSelection(p); + profileStore = MainApp.getConfigBuilder().getActiveProfileInterface().getProfile(); + if (profileStore == null) { + if (options.eventType == R.id.careportal_profileswitch) { + log.error("Profile switch called but plugin doesn't contain valid profile"); + } + } else { + ArrayList profileList; + units = profile != null ? profile.getUnits() : Constants.MGDL; + profileList = profileStore.getProfileList(); + ArrayAdapter adapter = new ArrayAdapter<>(getContext(), + R.layout.spinner_centered, profileList); + profileSpinner.setAdapter(adapter); + // set selected to actual profile + for (int p = 0; p < profileList.size(); p++) { + if (profileList.get(p).equals(MainApp.getConfigBuilder().getProfileName(false))) + profileSpinner.setSelection(p); + } } - - final Double bg = Profile.fromMgdlToUnits(GlucoseStatus.getGlucoseStatusData() != null ? GlucoseStatus.getGlucoseStatusData().glucose : 0d, profile != null ? profile.getUnits() : Constants.MGDL); + final Double bg = Profile.fromMgdlToUnits(GlucoseStatus.getGlucoseStatusData() != null ? GlucoseStatus.getGlucoseStatusData().glucose : 0d, units); // temp target - final ArrayList reasonList = new ArrayList(); - reasonList.add(MainApp.sResources.getString(R.string.manual)); - reasonList.add(MainApp.sResources.getString(R.string.eatingsoon)); - reasonList.add(MainApp.sResources.getString(R.string.activity)); - ArrayAdapter adapterReason = new ArrayAdapter(getContext(), + final List reasonList = Lists.newArrayList( + MainApp.gs(R.string.manual), + MainApp.gs(R.string.eatingsoon), + MainApp.gs(R.string.activity), + MainApp.gs(R.string.hypo)); + ArrayAdapter adapterReason = new ArrayAdapter<>(getContext(), R.layout.spinner_centered, reasonList); reasonSpinner.setAdapter(adapterReason); reasonSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { - double defaultDuration = 0; + double defaultDuration; double defaultTarget = 0; if (profile != null) { - defaultTarget = bg.doubleValue(); + defaultTarget = bg; } boolean erase = false; - if (MainApp.sResources.getString(R.string.eatingsoon).equals(reasonList.get(position))) { - defaultDuration = SP.getDouble(R.string.key_eatingsoon_duration, 0d); - defaultTarget = SP.getDouble(R.string.key_eatingsoon_target, 0d); - ; - } else if (MainApp.sResources.getString(R.string.activity).equals(reasonList.get(position))) { - defaultDuration = SP.getDouble(R.string.key_activity_duration, 0d); - ; - defaultTarget = SP.getDouble(R.string.key_activity_target, 0d); - ; + String units = MainApp.getConfigBuilder().getProfileUnits(); + DefaultValueHelper helper = new DefaultValueHelper(); + if (MainApp.gs(R.string.eatingsoon).equals(reasonList.get(position))) { + defaultDuration = helper.determineEatingSoonTTDuration(); + defaultTarget = helper.determineEatingSoonTT(units); + } else if (MainApp.gs(R.string.activity).equals(reasonList.get(position))) { + defaultDuration = helper.determineActivityTTDuration(); + defaultTarget = helper.determineActivityTT(units); + } else if (MainApp.gs(R.string.hypo).equals(reasonList.get(position))) { + defaultDuration = helper.determineHypoTTDuration(); + defaultTarget = helper.determineHypoTT(units); } else { defaultDuration = 0; erase = true; } + if (defaultTarget != 0 || erase) { editTemptarget.setValue(defaultTarget); } @@ -256,19 +271,16 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick editBg.setParams(bg, 0d, 500d, 1d, new DecimalFormat("0"), false, bgTextWatcher); editTemptarget.setParams(bg, 0d, 500d, 1d, new DecimalFormat("0"), false); } - sensorRadioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Double bg = Profile.fromMgdlToUnits(GlucoseStatus.getGlucoseStatusData() != null ? GlucoseStatus.getGlucoseStatusData().glucose : 0d, profile.getUnits()); - editBg.setValue(bg); - } + sensorRadioButton.setOnCheckedChangeListener((buttonView, isChecked) -> { + Double bg1 = Profile.fromMgdlToUnits(GlucoseStatus.getGlucoseStatusData() != null ? GlucoseStatus.getGlucoseStatusData().glucose : 0d, profile.getUnits()); + editBg.setValue(bg1); }); - Integer maxCarbs = MainApp.getConfigBuilder().applyCarbsConstraints(Constants.carbsOnlyForCheckLimit); + Integer maxCarbs = MainApp.getConstraintChecker().getMaxCarbsAllowed().value(); editCarbs = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_carbsinput); editCarbs.setParams(0d, 0d, (double) maxCarbs, 1d, new DecimalFormat("0"), false); - Double maxInsulin = MainApp.getConfigBuilder().applyBolusConstraints(Constants.bolusOnlyForCheckLimit); + Double maxInsulin = MainApp.getConstraintChecker().getMaxBolusAllowed().value(); editInsulin = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_insulininput); editInsulin.setParams(0d, 0d, maxInsulin, 0.05d, new DecimalFormat("0.00"), false); @@ -295,7 +307,9 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick } }; - Integer maxPercent = MainApp.getConfigBuilder().applyBasalConstraints(Constants.basalPercentOnlyForCheckLimit); + Integer maxPercent = 200; + if (profile != null) + maxPercent = MainApp.getConstraintChecker().getMaxBasalPercentAllowed(profile).value(); editPercent = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_percentinput); editPercent.setParams(0d, 0d, (double) maxPercent, 5d, new DecimalFormat("0"), true, percentTextWatcher); @@ -317,7 +331,9 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick } }; - Double maxAbsolute = MainApp.getConfigBuilder().applyBasalConstraints(Constants.basalAbsoluteOnlyForCheckLimit); + Double maxAbsolute = HardLimits.maxBasal(); + if (profile != null) + maxAbsolute = MainApp.getConstraintChecker().getMaxBasalAllowed(profile).value(); editAbsolute = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_absoluteinput); editAbsolute.setParams(0d, 0d, maxAbsolute, 0.05d, new DecimalFormat("0.00"), true, absoluteTextWatcher); @@ -330,19 +346,19 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick editTimeshift = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_timeshift); editTimeshift.setParams(0d, (double) Constants.CPP_MIN_TIMESHIFT, (double) Constants.CPP_MAX_TIMESHIFT, 1d, new DecimalFormat("0"), false); - ProfileSwitch ps = MainApp.getConfigBuilder().getProfileSwitchFromHistory(System.currentTimeMillis()); + ProfileSwitch ps = TreatmentsPlugin.getPlugin().getProfileSwitchFromHistory(DateUtil.now()); if (ps != null && ps.isCPP) { final int percentage = ps.percentage; final int timeshift = ps.timeshift; reuseButton.setText(reuseButton.getText() + " " + percentage + "% " + timeshift + "h"); - reuseButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - editPercentage.setValue((double) percentage); - editTimeshift.setValue((double) timeshift); - } + reuseButton.setOnClickListener(v -> { + editPercentage.setValue((double) percentage); + editTimeshift.setValue((double) timeshift); }); } + if (ps == null) { + options.duration = false; + } showOrHide((ViewGroup) view.findViewById(R.id.careportal_newnstreatment_eventtime_layout), options.date); showOrHide((ViewGroup) view.findViewById(R.id.careportal_newnstreatment_bg_layout), options.bg); @@ -393,7 +409,7 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick tpd.show(context.getFragmentManager(), "Timepickerdialog"); break; case R.id.ok: - createNSTreatment(); + confirmNSTreatmentCreation(); dismiss(); break; case R.id.cancel: @@ -407,20 +423,32 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick else layout.setVisibility(View.GONE); } + private void updateBGforDateTime() { + long millis = eventTime.getTime() - (150 * 1000L); // 2,5 * 60 * 1000 + List data = MainApp.getDbHelper().getBgreadingsDataFromTime(millis, true); + if ((data.size() > 0) && + (data.get(0).date > millis - 7 * 60 * 1000L) && + (data.get(0).date < millis + 7 * 60 * 1000L)) { + editBg.setValue(Profile.fromMgdlToUnits(data.get(0).value, profile != null ? profile.getUnits() : Constants.MGDL)); + } + } + @Override public void onDateSet(DatePickerDialog view, int year, int monthOfYear, int dayOfMonth) { eventTime.setYear(year - 1900); eventTime.setMonth(monthOfYear); eventTime.setDate(dayOfMonth); dateButton.setText(DateUtil.dateString(eventTime)); + updateBGforDateTime(); } @Override public void onTimeSet(RadialPickerLayout view, int hourOfDay, int minute, int second) { eventTime.setHours(hourOfDay); eventTime.setMinutes(minute); - eventTime.setSeconds(second); + eventTime.setSeconds(this.seconds++); // randomize seconds to prevent creating record of the same time, if user choose time manually timeButton.setText(DateUtil.timeString(eventTime)); + updateBGforDateTime(); } @@ -545,159 +573,151 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick String buildConfirmText(JSONObject data) { String ret = ""; - try { - if (data.has("eventType")) { - ret += getString(R.string.careportal_newnstreatment_eventtype); - ret += ": "; - ret += Translator.translate(data.getString("eventType")); - ret += "\n"; - } - if (data.has("glucose")) { - ret += getString(R.string.treatments_wizard_bg_label); - ret += ": "; - ret += data.get("glucose"); - ret += " " + units + "\n"; - } - if (data.has("glucoseType")) { - ret += getString(R.string.careportal_newnstreatment_glucosetype); - ret += ": "; - ret += Translator.translate(data.getString("glucoseType")); - ret += "\n"; - } - if (data.has("carbs")) { - ret += getString(R.string.careportal_newnstreatment_carbs_label); - ret += ": "; - ret += data.get("carbs"); - ret += " g\n"; - } - if (data.has("insulin")) { - ret += getString(R.string.careportal_newnstreatment_insulin_label); - ret += ": "; - ret += data.get("insulin"); - ret += " U\n"; - } - if (data.has("duration")) { - ret += getString(R.string.careportal_newnstreatment_duration_label); - ret += ": "; - ret += data.get("duration"); - ret += " min\n"; - } - if (data.has("percent")) { - ret += getString(R.string.careportal_newnstreatment_percent_label); - ret += ": "; - ret += data.get("percent"); - ret += " %\n"; - } - if (data.has("absolute")) { - ret += getString(R.string.careportal_newnstreatment_absolute_label); - ret += ": "; - ret += data.get("absolute"); - ret += " U/h\n"; - } - if (data.has("preBolus")) { - ret += getString(R.string.careportal_newnstreatment_carbtime_label); - ret += ": "; - ret += data.get("preBolus"); - ret += " min\n"; - } - if (data.has("notes")) { - ret += getString(R.string.careportal_newnstreatment_notes_label); - ret += ": "; - ret += data.get("notes"); - ret += "\n"; - } - if (data.has("profile")) { - ret += getString(R.string.careportal_newnstreatment_profile_label); - ret += ": "; - ret += data.get("profile"); - ret += "\n"; - } - if (data.has("percentage")) { - ret += getString(R.string.careportal_newnstreatment_percentage_label); - ret += ": "; - ret += data.get("percentage"); - ret += " %\n"; - } - if (data.has("timeshift")) { - ret += getString(R.string.careportal_newnstreatment_timeshift_label); - ret += ": "; - ret += data.get("timeshift"); - ret += " h\n"; - } - if (data.has("targetBottom") && data.has("targetTop")) { - ret += getString(R.string.target_range); - ret += " "; - ret += data.get("targetBottom"); - ret += " - "; - ret += data.get("targetTop"); - ret += "\n"; - } - if (data.has("created_at")) { - ret += getString(R.string.careportal_newnstreatment_eventtime_label); - ret += ": "; - ret += eventTime.toLocaleString(); - ret += "\n"; - } - if (data.has("enteredBy")) { - ret += getString(R.string.careportal_newnstreatment_enteredby_title); - ret += ": "; - ret += data.get("enteredBy"); - ret += "\n"; - } - } catch (JSONException e) { - log.error("Unhandled exception", e); + if (data.has("eventType")) { + ret += MainApp.gs(R.string.careportal_newnstreatment_eventtype); + ret += ": "; + ret += Translator.translate(JsonHelper.safeGetString(data, "eventType", "")); + ret += "\n"; + } + if (data.has("glucose")) { + ret += MainApp.gs(R.string.treatments_wizard_bg_label); + ret += ": "; + ret += JsonHelper.safeGetObject(data, "glucose", ""); + ret += " " + units + "\n"; + } + if (data.has("glucoseType")) { + ret += MainApp.gs(R.string.careportal_newnstreatment_glucosetype); + ret += ": "; + ret += Translator.translate(JsonHelper.safeGetString(data, "glucoseType", "")); + ret += "\n"; + } + if (data.has("carbs")) { + ret += MainApp.gs(R.string.careportal_newnstreatment_carbs_label); + ret += ": "; + ret += JsonHelper.safeGetObject(data, "carbs", ""); + ret += " g\n"; + } + if (data.has("insulin")) { + ret += MainApp.gs(R.string.careportal_newnstreatment_insulin_label); + ret += ": "; + ret += JsonHelper.safeGetObject(data, "insulin", ""); + ret += " U\n"; + } + if (data.has("duration")) { + ret += MainApp.gs(R.string.careportal_newnstreatment_duration_label); + ret += ": "; + ret += JsonHelper.safeGetObject(data, "duration", ""); + ret += " min\n"; + } + if (data.has("percent")) { + ret += MainApp.gs(R.string.careportal_newnstreatment_percent_label); + ret += ": "; + ret += JsonHelper.safeGetObject(data, "percent", ""); + ret += " %\n"; + } + if (data.has("absolute")) { + ret += MainApp.gs(R.string.careportal_newnstreatment_absolute_label); + ret += ": "; + ret += JsonHelper.safeGetObject(data, "absolute", ""); + ret += " U/h\n"; + } + if (data.has("preBolus")) { + ret += MainApp.gs(R.string.careportal_newnstreatment_carbtime_label); + ret += ": "; + ret += JsonHelper.safeGetObject(data, "preBolus", ""); + ret += " min\n"; + } + if (data.has("notes")) { + ret += MainApp.gs(R.string.careportal_newnstreatment_notes_label); + ret += ": "; + ret += JsonHelper.safeGetObject(data, "notes", ""); + ret += "\n"; + } + if (data.has("profile")) { + ret += MainApp.gs(R.string.careportal_newnstreatment_profile_label); + ret += ": "; + ret += JsonHelper.safeGetObject(data, "profile", ""); + ret += "\n"; + } + if (data.has("percentage")) { + ret += MainApp.gs(R.string.careportal_newnstreatment_percentage_label); + ret += ": "; + ret += JsonHelper.safeGetObject(data, "percentage", ""); + ret += " %\n"; + } + if (data.has("timeshift")) { + ret += MainApp.gs(R.string.careportal_newnstreatment_timeshift_label); + ret += ": "; + ret += JsonHelper.safeGetObject(data, "timeshift", ""); + ret += " h\n"; + } + if (data.has("targetBottom") && data.has("targetTop")) { + ret += MainApp.gs(R.string.target_range); + ret += " "; + ret += JsonHelper.safeGetObject(data, "targetBottom", ""); + ret += " - "; + ret += JsonHelper.safeGetObject(data, "targetTop", ""); + ret += "\n"; + } + if (data.has("created_at")) { + ret += MainApp.gs(R.string.careportal_newnstreatment_eventtime_label); + ret += ": "; + ret += eventTime.toLocaleString(); + ret += "\n"; + } + if (data.has("enteredBy")) { + ret += MainApp.gs(R.string.careportal_newnstreatment_enteredby_title); + ret += ": "; + ret += JsonHelper.safeGetObject(data, "enteredBy", ""); + ret += "\n"; } return ret; } - void createNSTreatment() { - final JSONObject data = gatherData(); - String confirmText = buildConfirmText(data); - AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); - builder.setTitle(getContext().getString(R.string.confirmation)); - builder.setMessage(confirmText); - builder.setPositiveButton(getContext().getString(R.string.ok), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - if (options.executeProfileSwitch) { - if (data.has("profile")) { - try { - doProfileSwitch(profileStore, data.getString("profile"), data.getInt("duration"), data.getInt("percentage"), data.getInt("timeshift")); - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - } - } else if (options.executeTempTarget) { - try { - if ((data.has("targetBottom") && data.has("targetTop")) || (data.has("duration") && data.getInt("duration") == 0)) { - TempTarget tempTarget = new TempTarget(); - tempTarget.date = eventTime.getTime(); - tempTarget.durationInMinutes = data.getInt("duration"); - tempTarget.reason = data.getString("reason"); - tempTarget.source = Source.USER; - if (tempTarget.durationInMinutes != 0) { - tempTarget.low = Profile.toMgdl(data.getDouble("targetBottom"), profile.getUnits()); - tempTarget.high = Profile.toMgdl(data.getDouble("targetTop"), profile.getUnits()); - } else { - tempTarget.low = 0; - tempTarget.high = 0; - } - log.debug("Creating new TempTarget db record: " + tempTarget.toString()); - MainApp.getDbHelper().createOrUpdate(tempTarget); - NSUpload.uploadCareportalEntryToNS(data); - Answers.getInstance().logCustom(new CustomEvent("TempTarget")); - } - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - } else { - NSUpload.uploadCareportalEntryToNS(data); - Answers.getInstance().logCustom(new CustomEvent("NSTreatment")); - } + void confirmNSTreatmentCreation() { + if (context != null) { + final JSONObject data = gatherData(); + final String confirmText = buildConfirmText(data); + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(MainApp.gs(R.string.confirmation)); + builder.setMessage(confirmText); + builder.setPositiveButton(MainApp.gs(R.string.ok), (dialog, id) -> createNSTreatment(data)); + builder.setNegativeButton(MainApp.gs(R.string.cancel), null); + builder.show(); + } + } + + + public void createNSTreatment(JSONObject data) { + if (options.executeProfileSwitch) { + if (data.has("profile")) { + doProfileSwitch(profileStore, JsonHelper.safeGetString(data, "profile"), JsonHelper.safeGetInt(data, "duration"), JsonHelper.safeGetInt(data, "percentage"), JsonHelper.safeGetInt(data, "timeshift")); } - }); - builder.setNegativeButton(getContext().getString(R.string.cancel), null); - builder.show(); + } else if (options.executeTempTarget) { + final int duration = JsonHelper.safeGetInt(data, "duration"); + final double targetBottom = JsonHelper.safeGetDouble(data, "targetBottom"); + final double targetTop = JsonHelper.safeGetDouble(data, "targetTop"); + final String reason = JsonHelper.safeGetString(data, "reason", ""); + if ((targetBottom != 0d && targetTop != 0d) || duration == 0) { + TempTarget tempTarget = new TempTarget() + .date(eventTime.getTime()) + .duration(duration) + .reason(reason) + .source(Source.USER); + if (tempTarget.durationInMinutes != 0) { + tempTarget.low(Profile.toMgdl(targetBottom, profile.getUnits())) + .high(Profile.toMgdl(targetTop, profile.getUnits())); + } else { + tempTarget.low(0).high(0); + } + TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget); + FabricPrivacy.getInstance().logCustom(new CustomEvent("TempTarget")); + } + } else { + NSUpload.uploadCareportalEntryToNS(data); + FabricPrivacy.getInstance().logCustom(new CustomEvent("NSTreatment")); + } } public static void doProfileSwitch(final ProfileStore profileStore, final String profileName, final int duration, final int percentage, final int timeshift) { @@ -706,60 +726,30 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick profileSwitch.source = Source.USER; profileSwitch.profileName = profileName; profileSwitch.profileJson = profileStore.getSpecificProfile(profileName).getData().toString(); - profileSwitch.profilePlugin = ConfigBuilderPlugin.getActiveProfileInterface().getClass().getName(); + profileSwitch.profilePlugin = MainApp.getConfigBuilder().getActiveProfileInterface().getClass().getName(); profileSwitch.durationInMinutes = duration; profileSwitch.isCPP = percentage != 100 || timeshift != 0; profileSwitch.timeshift = timeshift; profileSwitch.percentage = percentage; - MainApp.getConfigBuilder().addToHistoryProfileSwitch(profileSwitch); - - ConfigBuilderPlugin.getCommandQueue().setProfile(profileSwitch.getProfileObject(), new Callback() { - @Override - public void run() { - if (!result.success) { - Intent i = new Intent(MainApp.instance(), ErrorHelperActivity.class); - i.putExtra("soundid", R.raw.boluserror); - i.putExtra("status", result.comment); - i.putExtra("title", MainApp.sResources.getString(R.string.failedupdatebasalprofile)); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - MainApp.instance().startActivity(i); - } - MainApp.bus().post(new EventNewBasalProfile()); - } - }); - Answers.getInstance().logCustom(new CustomEvent("ProfileSwitch")); + TreatmentsPlugin.getPlugin().addToHistoryProfileSwitch(profileSwitch); + FabricPrivacy.getInstance().logCustom(new CustomEvent("ProfileSwitch")); } public static void doProfileSwitch(final int duration, final int percentage, final int timeshift) { - ProfileSwitch profileSwitch = MainApp.getConfigBuilder().getProfileSwitchFromHistory(System.currentTimeMillis()); + ProfileSwitch profileSwitch = TreatmentsPlugin.getPlugin().getProfileSwitchFromHistory(System.currentTimeMillis()); if (profileSwitch != null) { profileSwitch = new ProfileSwitch(); profileSwitch.date = System.currentTimeMillis(); profileSwitch.source = Source.USER; profileSwitch.profileName = MainApp.getConfigBuilder().getProfileName(System.currentTimeMillis(), false); profileSwitch.profileJson = MainApp.getConfigBuilder().getProfile().getData().toString(); - profileSwitch.profilePlugin = ConfigBuilderPlugin.getActiveProfileInterface().getClass().getName(); + profileSwitch.profilePlugin = MainApp.getConfigBuilder().getActiveProfileInterface().getClass().getName(); profileSwitch.durationInMinutes = duration; profileSwitch.isCPP = percentage != 100 || timeshift != 0; profileSwitch.timeshift = timeshift; profileSwitch.percentage = percentage; - MainApp.getConfigBuilder().addToHistoryProfileSwitch(profileSwitch); - - ConfigBuilderPlugin.getCommandQueue().setProfile(profileSwitch.getProfileObject(), new Callback() { - @Override - public void run() { - if (!result.success) { - Intent i = new Intent(MainApp.instance(), ErrorHelperActivity.class); - i.putExtra("soundid", R.raw.boluserror); - i.putExtra("status", result.comment); - i.putExtra("title", MainApp.sResources.getString(R.string.failedupdatebasalprofile)); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - MainApp.instance().startActivity(i); - } - MainApp.bus().post(new EventNewBasalProfile()); - } - }); - Answers.getInstance().logCustom(new CustomEvent("ProfileSwitch")); + TreatmentsPlugin.getPlugin().addToHistoryProfileSwitch(profileSwitch); + FabricPrivacy.getInstance().logCustom(new CustomEvent("ProfileSwitch")); } else { log.error("No profile switch existing"); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderFragment.java index 2e620eab70..829a0fa4ad 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderFragment.java @@ -4,7 +4,7 @@ package info.nightscout.androidaps.plugins.ConfigBuilder; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.Fragment; +import android.support.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -17,12 +17,14 @@ import android.widget.ListAdapter; import android.widget.ListView; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import java.util.ArrayList; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.PreferencesActivity; import info.nightscout.androidaps.R; @@ -33,43 +35,59 @@ import info.nightscout.androidaps.interfaces.BgSourceInterface; import info.nightscout.androidaps.interfaces.ConstraintsInterface; import info.nightscout.androidaps.interfaces.InsulinInterface; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.ProfileInterface; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.interfaces.SensitivityInterface; -import info.nightscout.androidaps.plugins.Insulin.InsulinFastactingPlugin; +import info.nightscout.androidaps.plugins.Common.SubscriberFragment; +import info.nightscout.androidaps.plugins.Insulin.InsulinOrefRapidActingPlugin; import info.nightscout.androidaps.plugins.ProfileNS.NSProfilePlugin; import info.nightscout.androidaps.plugins.PumpVirtual.VirtualPumpPlugin; import info.nightscout.androidaps.plugins.SensitivityOref0.SensitivityOref0Plugin; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.PasswordProtection; -public class ConfigBuilderFragment extends Fragment { - - static ConfigBuilderPlugin configBuilderPlugin = new ConfigBuilderPlugin(); - - static public ConfigBuilderPlugin getPlugin() { - return configBuilderPlugin; - } +public class ConfigBuilderFragment extends SubscriberFragment { + @BindView(R.id.configbuilder_insulinlistview) ListView insulinListView; + @BindView(R.id.configbuilder_sensitivitylistview) ListView sensitivityListView; + @BindView(R.id.configbuilder_bgsourcelistview) ListView bgsourceListView; + @BindView(R.id.configbuilder_bgsourcelabel) TextView bgsourceLabel; + @BindView(R.id.configbuilder_pumplistview) ListView pumpListView; + @BindView(R.id.configbuilder_pumplabel) TextView pumpLabel; + @BindView(R.id.configbuilder_looplistview) ListView loopListView; + @BindView(R.id.configbuilder_looplabel) TextView loopLabel; + @BindView(R.id.configbuilder_treatmentslistview) ListView treatmentsListView; + @BindView(R.id.configbuilder_treatmentslabel) TextView treatmentsLabel; + @BindView(R.id.configbuilder_profilelistview) ListView profileListView; + @BindView(R.id.configbuilder_profilelabel) TextView profileLabel; + @BindView(R.id.configbuilder_apslistview) ListView apsListView; + @BindView(R.id.configbuilder_apslabel) TextView apsLabel; + @BindView(R.id.configbuilder_constraintslistview) ListView constraintsListView; + @BindView(R.id.configbuilder_constraintslabel) TextView constraintsLabel; + @BindView(R.id.configbuilder_generallistview) ListView generalListView; + @BindView(R.id.configbuilder_mainlayout) LinearLayout mainLayout; + @BindView(R.id.configbuilder_unlock) Button unlock; PluginCustomAdapter insulinDataAdapter = null; @@ -84,105 +102,83 @@ public class ConfigBuilderFragment extends Fragment { PluginCustomAdapter generalDataAdapter = null; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { try { View view = inflater.inflate(R.layout.configbuilder_fragment, container, false); - insulinListView = (ListView) view.findViewById(R.id.configbuilder_insulinlistview); - sensitivityListView = (ListView) view.findViewById(R.id.configbuilder_sensitivitylistview); - bgsourceListView = (ListView) view.findViewById(R.id.configbuilder_bgsourcelistview); - bgsourceLabel = (TextView) view.findViewById(R.id.configbuilder_bgsourcelabel); - pumpListView = (ListView) view.findViewById(R.id.configbuilder_pumplistview); - pumpLabel = (TextView) view.findViewById(R.id.configbuilder_pumplabel); - loopListView = (ListView) view.findViewById(R.id.configbuilder_looplistview); - loopLabel = (TextView) view.findViewById(R.id.configbuilder_looplabel); - treatmentsListView = (ListView) view.findViewById(R.id.configbuilder_treatmentslistview); - treatmentsLabel = (TextView) view.findViewById(R.id.configbuilder_treatmentslabel); - profileListView = (ListView) view.findViewById(R.id.configbuilder_profilelistview); - profileLabel = (TextView) view.findViewById(R.id.configbuilder_profilelabel); - apsListView = (ListView) view.findViewById(R.id.configbuilder_apslistview); - apsLabel = (TextView) view.findViewById(R.id.configbuilder_apslabel); - constraintsListView = (ListView) view.findViewById(R.id.configbuilder_constraintslistview); - constraintsLabel = (TextView) view.findViewById(R.id.configbuilder_constraintslabel); - generalListView = (ListView) view.findViewById(R.id.configbuilder_generallistview); + unbinder = ButterKnife.bind(this, view); - mainLayout = (LinearLayout) view.findViewById(R.id.configbuilder_mainlayout); - unlock = (Button) view.findViewById(R.id.configbuilder_unlock); - - setViews(); - - if (PasswordProtection.isLocked("settings_password")) { + if (PasswordProtection.isLocked("settings_password")) mainLayout.setVisibility(View.GONE); - unlock.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - PasswordProtection.QueryPassword(getContext(), R.string.settings_password, "settings_password", new Runnable() { - @Override - public void run() { - mainLayout.setVisibility(View.VISIBLE); - unlock.setVisibility(View.GONE); - } - }, null); - } - }); - } else { + else unlock.setVisibility(View.GONE); - } return view; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } return null; } - void setViews() { - insulinDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInListByInterface(InsulinInterface.class, PluginBase.INSULIN), PluginBase.INSULIN); + @OnClick(R.id.configbuilder_unlock) + public void onClickUnlock() { + PasswordProtection.QueryPassword(getContext(), R.string.settings_password, "settings_password", () -> { + mainLayout.setVisibility(View.VISIBLE); + unlock.setVisibility(View.GONE); + }, null); + } + + + @Override + protected void updateGUI() { + + insulinDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInListByInterface(InsulinInterface.class, PluginType.INSULIN), PluginType.INSULIN); insulinListView.setAdapter(insulinDataAdapter); setListViewHeightBasedOnChildren(insulinListView); - bgsourceDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInListByInterface(BgSourceInterface.class, PluginBase.BGSOURCE), PluginBase.BGSOURCE); + bgsourceDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInListByInterface(BgSourceInterface.class, PluginType.BGSOURCE), PluginType.BGSOURCE); bgsourceListView.setAdapter(bgsourceDataAdapter); - if (MainApp.getSpecificPluginsVisibleInList(PluginBase.BGSOURCE).size() == 0) + if (MainApp.getSpecificPluginsVisibleInList(PluginType.BGSOURCE).size() == 0) bgsourceLabel.setVisibility(View.GONE); setListViewHeightBasedOnChildren(bgsourceListView); - pumpDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInList(PluginBase.PUMP), PluginBase.PUMP); + pumpDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInList(PluginType.PUMP), PluginType.PUMP); pumpListView.setAdapter(pumpDataAdapter); - if (MainApp.getSpecificPluginsVisibleInList(PluginBase.PUMP).size() == 0) + if (MainApp.getSpecificPluginsVisibleInList(PluginType.PUMP).size() == 0 || Config.NSCLIENT || Config.G5UPLOADER) { pumpLabel.setVisibility(View.GONE); + pumpListView.setVisibility(View.GONE); + } setListViewHeightBasedOnChildren(pumpListView); - loopDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInList(PluginBase.LOOP), PluginBase.LOOP); + loopDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInList(PluginType.LOOP), PluginType.LOOP); loopListView.setAdapter(loopDataAdapter); setListViewHeightBasedOnChildren(loopListView); - if (MainApp.getSpecificPluginsVisibleInList(PluginBase.LOOP).size() == 0) + if (MainApp.getSpecificPluginsVisibleInList(PluginType.LOOP).size() == 0) loopLabel.setVisibility(View.GONE); - treatmentDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInList(PluginBase.TREATMENT), PluginBase.TREATMENT); + treatmentDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInList(PluginType.TREATMENT), PluginType.TREATMENT); treatmentsListView.setAdapter(treatmentDataAdapter); setListViewHeightBasedOnChildren(treatmentsListView); - if (MainApp.getSpecificPluginsVisibleInList(PluginBase.TREATMENT).size() == 0) + if (MainApp.getSpecificPluginsVisibleInList(PluginType.TREATMENT).size() == 0) treatmentsLabel.setVisibility(View.GONE); - profileDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInListByInterface(ProfileInterface.class, PluginBase.PROFILE), PluginBase.PROFILE); + profileDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInListByInterface(ProfileInterface.class, PluginType.PROFILE), PluginType.PROFILE); profileListView.setAdapter(profileDataAdapter); - if (MainApp.getSpecificPluginsVisibleInList(PluginBase.PROFILE).size() == 0) + if (MainApp.getSpecificPluginsVisibleInList(PluginType.PROFILE).size() == 0) profileLabel.setVisibility(View.GONE); setListViewHeightBasedOnChildren(profileListView); - apsDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInList(PluginBase.APS), PluginBase.APS); + apsDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInList(PluginType.APS), PluginType.APS); apsListView.setAdapter(apsDataAdapter); setListViewHeightBasedOnChildren(apsListView); - if (MainApp.getSpecificPluginsVisibleInList(PluginBase.APS).size() == 0) + if (MainApp.getSpecificPluginsVisibleInList(PluginType.APS).size() == 0) apsLabel.setVisibility(View.GONE); - sensivityDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInListByInterface(SensitivityInterface.class, PluginBase.SENSITIVITY), PluginBase.SENSITIVITY); + sensivityDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInListByInterface(SensitivityInterface.class, PluginType.SENSITIVITY), PluginType.SENSITIVITY); sensitivityListView.setAdapter(sensivityDataAdapter); setListViewHeightBasedOnChildren(sensitivityListView); - constraintsDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInListByInterface(ConstraintsInterface.class, PluginBase.CONSTRAINTS), PluginBase.CONSTRAINTS); + constraintsDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInListByInterface(ConstraintsInterface.class, PluginType.CONSTRAINTS), PluginType.CONSTRAINTS); constraintsListView.setAdapter(constraintsDataAdapter); setListViewHeightBasedOnChildren(constraintsListView); - if (MainApp.getSpecificPluginsVisibleInList(PluginBase.CONSTRAINTS).size() == 0) + if (MainApp.getSpecificPluginsVisibleInList(PluginType.CONSTRAINTS).size() == 0) constraintsLabel.setVisibility(View.GONE); - generalDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInList(PluginBase.GENERAL), PluginBase.GENERAL); + generalDataAdapter = new PluginCustomAdapter(getContext(), R.layout.configbuilder_simpleitem, MainApp.getSpecificPluginsVisibleInList(PluginType.GENERAL), PluginType.GENERAL); generalListView.setAdapter(generalDataAdapter); setListViewHeightBasedOnChildren(generalListView); - } /* @@ -192,10 +188,10 @@ public class ConfigBuilderFragment extends Fragment { private class PluginCustomAdapter extends ArrayAdapter { private ArrayList pluginList; - final private int type; + final private PluginType type; - public PluginCustomAdapter(Context context, int textViewResourceId, - ArrayList pluginList, int type) { + PluginCustomAdapter(Context context, int textViewResourceId, + ArrayList pluginList, PluginType type) { super(context, textViewResourceId, pluginList); this.pluginList = new ArrayList<>(); this.pluginList.addAll(pluginList); @@ -209,10 +205,11 @@ public class ConfigBuilderFragment extends Fragment { ImageView settings; } + @NonNull @Override - public View getView(int position, View view, ViewGroup parent) { + public View getView(int position, View view, @NonNull ViewGroup parent) { - PluginViewHolder holder = null; + PluginViewHolder holder; PluginBase plugin = pluginList.get(position); if (view == null) { @@ -231,60 +228,45 @@ public class ConfigBuilderFragment extends Fragment { view.setTag(holder); - holder.checkboxEnabled.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - CheckBox cb = (CheckBox) v; - PluginBase plugin = (PluginBase) cb.getTag(); - plugin.setFragmentEnabled(type, cb.isChecked()); - plugin.setFragmentVisible(type, cb.isChecked()); - onEnabledCategoryChanged(plugin, type); - configBuilderPlugin.storeSettings(); - MainApp.bus().post(new EventRefreshGui()); - MainApp.bus().post(new EventConfigBuilderChange()); - getPlugin().logPluginStatus(); - Answers.getInstance().logCustom(new CustomEvent("ConfigurationChange")); - } + holder.checkboxEnabled.setOnClickListener(v -> { + CheckBox cb = (CheckBox) v; + PluginBase plugin1 = (PluginBase) cb.getTag(); + plugin1.setPluginEnabled(type, cb.isChecked()); + plugin1.setFragmentVisible(type, cb.isChecked()); + onEnabledCategoryChanged(plugin1, type); + ConfigBuilderPlugin.getPlugin().storeSettings("CheckedCheckboxEnabled"); + MainApp.bus().post(new EventRefreshGui()); + MainApp.bus().post(new EventConfigBuilderChange()); + ConfigBuilderPlugin.getPlugin().logPluginStatus(); + FabricPrivacy.getInstance().logCustom(new CustomEvent("ConfigurationChange")); }); - holder.checkboxVisible.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - CheckBox cb = (CheckBox) v; - PluginBase plugin = (PluginBase) cb.getTag(); - plugin.setFragmentVisible(type, cb.isChecked()); - configBuilderPlugin.storeSettings(); - MainApp.bus().post(new EventRefreshGui()); - getPlugin().logPluginStatus(); - } + holder.checkboxVisible.setOnClickListener(v -> { + CheckBox cb = (CheckBox) v; + PluginBase plugin12 = (PluginBase) cb.getTag(); + plugin12.setFragmentVisible(type, cb.isChecked()); + ConfigBuilderPlugin.getPlugin().storeSettings("CheckedCheckboxVisible"); + MainApp.bus().post(new EventRefreshGui()); + ConfigBuilderPlugin.getPlugin().logPluginStatus(); }); - holder.settings.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - final PluginBase plugin = (PluginBase) v.getTag(); - PasswordProtection.QueryPassword(getContext(), R.string.settings_password, "settings_password", new Runnable() { - @Override - public void run() { - Intent i = new Intent(getContext(), PreferencesActivity.class); - i.putExtra("id", plugin.getPreferencesId()); - startActivity(i); - } - }, null); - } + holder.settings.setOnClickListener(v -> { + final PluginBase plugin13 = (PluginBase) v.getTag(); + PasswordProtection.QueryPassword(getContext(), R.string.settings_password, "settings_password", () -> { + Intent i = new Intent(getContext(), PreferencesActivity.class); + i.putExtra("id", plugin13.getPreferencesId()); + startActivity(i); + }, null); }); - holder.name.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - final PluginBase plugin = (PluginBase) v.getTag(); - PasswordProtection.QueryPassword(getContext(), R.string.settings_password, "settings_password", new Runnable() { - @Override - public void run() { - Intent i = new Intent(getContext(), PreferencesActivity.class); - i.putExtra("id", plugin.getPreferencesId()); - startActivity(i); - } - }, null); - return false; - } + holder.name.setOnLongClickListener(v -> { + final PluginBase plugin14 = (PluginBase) v.getTag(); + PasswordProtection.QueryPassword(getContext(), R.string.settings_password, "settings_password", () -> { + Intent i = new Intent(getContext(), PreferencesActivity.class); + i.putExtra("id", plugin14.getPreferencesId()); + startActivity(i); + }, null); + return false; }); } else { @@ -293,15 +275,18 @@ public class ConfigBuilderFragment extends Fragment { holder.name.setText(plugin.getName()); holder.checkboxEnabled.setChecked(plugin.isEnabled(type)); - holder.checkboxVisible.setChecked(plugin.isVisibleInTabs(type)); + holder.checkboxVisible.setChecked(plugin.isFragmentVisible()); holder.name.setTag(plugin); holder.checkboxEnabled.setTag(plugin); holder.checkboxVisible.setTag(plugin); holder.settings.setTag(plugin); - if (!plugin.canBeHidden(type)) { + if (plugin.pluginDescription.alwaysEnabled) { + holder.checkboxEnabled.setEnabled(false); + } + + if (plugin.pluginDescription.alwayVisible) { holder.checkboxEnabled.setEnabled(false); - holder.checkboxVisible.setEnabled(false); } if (!plugin.isEnabled(type)) { @@ -313,19 +298,19 @@ public class ConfigBuilderFragment extends Fragment { } // Hide enabled control and force enabled plugin if there is only one plugin available - if (type == PluginBase.INSULIN || type == PluginBase.PUMP || type == PluginBase.TREATMENT || type == PluginBase.PROFILE || type == PluginBase.SENSITIVITY) + if (type == PluginType.INSULIN || type == PluginType.PUMP || type == PluginType.SENSITIVITY) if (pluginList.size() < 2) { holder.checkboxEnabled.setEnabled(false); - plugin.setFragmentEnabled(type, true); - getPlugin().storeSettings(); + plugin.setPluginEnabled(type, true); + ConfigBuilderPlugin.getPlugin().storeSettings("ForceEnable"); } // Constraints cannot be disabled - if (type == PluginBase.CONSTRAINTS) + if (type == PluginType.CONSTRAINTS) holder.checkboxEnabled.setEnabled(false); // Hide disabled profiles by default - if (type == PluginBase.PROFILE) { + if (type == PluginType.PROFILE) { if (!plugin.isEnabled(type)) { holder.checkboxVisible.setEnabled(false); holder.checkboxVisible.setChecked(false); @@ -335,9 +320,9 @@ public class ConfigBuilderFragment extends Fragment { } // Disable profile control for pump profiles if pump is not enabled - if (type == PluginBase.PROFILE) { + if (type == PluginType.PROFILE) { if (PumpInterface.class.isAssignableFrom(plugin.getClass())) { - if (!plugin.isEnabled(PluginBase.PUMP)) { + if (!plugin.isEnabled(PluginType.PUMP)) { holder.checkboxEnabled.setEnabled(false); holder.checkboxEnabled.setChecked(false); } @@ -354,32 +339,32 @@ public class ConfigBuilderFragment extends Fragment { } - void onEnabledCategoryChanged(PluginBase changedPlugin, int type) { + void onEnabledCategoryChanged(PluginBase changedPlugin, PluginType type) { ArrayList pluginsInCategory = null; switch (type) { // Multiple selection allowed - case PluginBase.GENERAL: - case PluginBase.CONSTRAINTS: - case PluginBase.LOOP: + case GENERAL: + case CONSTRAINTS: + case LOOP: break; // Single selection allowed - case PluginBase.INSULIN: + case INSULIN: pluginsInCategory = MainApp.getSpecificPluginsListByInterface(InsulinInterface.class); break; - case PluginBase.SENSITIVITY: + case SENSITIVITY: pluginsInCategory = MainApp.getSpecificPluginsListByInterface(SensitivityInterface.class); break; - case PluginBase.APS: + case APS: pluginsInCategory = MainApp.getSpecificPluginsListByInterface(APSInterface.class); break; - case PluginBase.PROFILE: + case PROFILE: pluginsInCategory = MainApp.getSpecificPluginsListByInterface(ProfileInterface.class); break; - case PluginBase.BGSOURCE: + case BGSOURCE: pluginsInCategory = MainApp.getSpecificPluginsListByInterface(BgSourceInterface.class); break; - case PluginBase.TREATMENT: - case PluginBase.PUMP: + case TREATMENT: + case PUMP: pluginsInCategory = MainApp.getSpecificPluginsListByInterface(PumpInterface.class); break; } @@ -390,23 +375,23 @@ public class ConfigBuilderFragment extends Fragment { if (p.getName().equals(changedPlugin.getName())) { // this is new selected } else { - p.setFragmentEnabled(type, false); + p.setPluginEnabled(type, false); p.setFragmentVisible(type, false); } } } else { // enable first plugin in list - if (type == PluginBase.PUMP) - MainApp.getSpecificPlugin(VirtualPumpPlugin.class).setFragmentEnabled(type, true); - else if (type == PluginBase.INSULIN) - MainApp.getSpecificPlugin(InsulinFastactingPlugin.class).setFragmentEnabled(type, true); - else if (type == PluginBase.SENSITIVITY) - MainApp.getSpecificPlugin(SensitivityOref0Plugin.class).setFragmentEnabled(type, true); - else if (type == PluginBase.PROFILE) - MainApp.getSpecificPlugin(NSProfilePlugin.class).setFragmentEnabled(type, true); + if (type == PluginType.PUMP) + VirtualPumpPlugin.getPlugin().setPluginEnabled(type, true); + else if (type == PluginType.INSULIN) + InsulinOrefRapidActingPlugin.getPlugin().setPluginEnabled(type, true); + else if (type == PluginType.SENSITIVITY) + SensitivityOref0Plugin.getPlugin().setPluginEnabled(type, true); + else if (type == PluginType.PROFILE) + NSProfilePlugin.getPlugin().setPluginEnabled(type, true); else - pluginsInCategory.get(0).setFragmentEnabled(type, true); + pluginsInCategory.get(0).setPluginEnabled(type, true); } - setViews(); + updateGUI(); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderPlugin.java index e63924d80a..df5b727749 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderPlugin.java @@ -1,60 +1,71 @@ package info.nightscout.androidaps.plugins.ConfigBuilder; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; +import android.content.Intent; import android.support.annotation.Nullable; -import org.json.JSONException; -import org.json.JSONObject; +import com.crashlytics.android.answers.CustomEvent; +import com.squareup.otto.Subscribe; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; -import java.util.List; +import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; -import info.nightscout.androidaps.data.Intervals; -import info.nightscout.androidaps.data.IobTotal; -import info.nightscout.androidaps.data.MealData; import info.nightscout.androidaps.data.Profile; -import info.nightscout.androidaps.data.ProfileIntervals; +import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.data.PumpEnactResult; -import info.nightscout.androidaps.db.ExtendedBolus; +import info.nightscout.androidaps.db.CareportalEvent; import info.nightscout.androidaps.db.ProfileSwitch; -import info.nightscout.androidaps.db.TempTarget; +import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.db.TemporaryBasal; -import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventAppInitialized; +import info.nightscout.androidaps.events.EventNewBasalProfile; +import info.nightscout.androidaps.events.EventProfileSwitchChange; import info.nightscout.androidaps.interfaces.APSInterface; import info.nightscout.androidaps.interfaces.BgSourceInterface; -import info.nightscout.androidaps.interfaces.ConstraintsInterface; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.InsulinInterface; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.ProfileInterface; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.interfaces.SensitivityInterface; import info.nightscout.androidaps.interfaces.TreatmentsInterface; +import info.nightscout.androidaps.plugins.Insulin.InsulinOrefRapidActingPlugin; import info.nightscout.androidaps.plugins.Loop.APSResult; import info.nightscout.androidaps.plugins.Loop.LoopPlugin; -import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; +import info.nightscout.androidaps.plugins.Overview.Dialogs.ErrorHelperActivity; import info.nightscout.androidaps.plugins.PumpVirtual.VirtualPumpPlugin; +import info.nightscout.androidaps.plugins.SensitivityOref0.SensitivityOref0Plugin; import info.nightscout.androidaps.queue.Callback; import info.nightscout.androidaps.queue.CommandQueue; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.NSUpload; import info.nightscout.utils.SP; +import info.nightscout.utils.ToastUtils; /** * Created by mike on 05.08.2016. */ -public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, TreatmentsInterface { +public class ConfigBuilderPlugin extends PluginBase { private static Logger log = LoggerFactory.getLogger(ConfigBuilderPlugin.class); - private static BgSourceInterface activeBgSource; + private static ConfigBuilderPlugin configBuilderPlugin; + + static public ConfigBuilderPlugin getPlugin() { + if (configBuilderPlugin == null) + configBuilderPlugin = new ConfigBuilderPlugin(); + return configBuilderPlugin; + } + + private BgSourceInterface activeBgSource; private static PumpInterface activePump; private static ProfileInterface activeProfile; private static TreatmentsInterface activeTreatments; @@ -73,131 +84,175 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr private static CommandQueue commandQueue = new CommandQueue(); public ConfigBuilderPlugin() { + super(new PluginDescription() + .mainType(PluginType.GENERAL) + .fragmentClass(ConfigBuilderFragment.class.getName()) + .showInList(false) + .alwaysEnabled(true) + .alwayVisible(true) + .pluginName(R.string.configbuilder) + .shortName(R.string.configbuilder_shortname) + ); + } + + @Override + protected void onStart() { MainApp.bus().register(this); + super.onStart(); } @Override - public int getType() { - return PluginBase.GENERAL; + protected void onStop() { + super.onStop(); + MainApp.bus().unregister(this); } - @Override - public String getFragmentClass() { - return ConfigBuilderFragment.class.getName(); - } - - @Override - public String getName() { - return MainApp.instance().getString(R.string.configbuilder); - } - - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.configbuilder_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == GENERAL; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == GENERAL; - } - - @Override - public boolean canBeHidden(int type) { - return false; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return false; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - // Always enabled - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - // Always visible - } - - @Override - public int getPreferencesId() { - return -1; - } public void initialize() { pluginList = MainApp.getPluginsList(); + upgradeSettings(); loadSettings(); MainApp.bus().post(new EventAppInitialized()); } - public void storeSettings() { + public void storeSettings(String from) { if (pluginList != null) { if (Config.logPrefsChange) - log.debug("Storing settings"); - SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(MainApp.instance().getApplicationContext()); - SharedPreferences.Editor editor = settings.edit(); + log.debug("Storing settings from: " + from); - for (int type = 1; type < PluginBase.LAST; type++) { - for (PluginBase p : pluginList) { - String settingEnabled = "ConfigBuilder_" + type + "_" + p.getClass().getSimpleName() + "_Enabled"; - String settingVisible = "ConfigBuilder_" + type + "_" + p.getClass().getSimpleName() + "_Visible"; - editor.putBoolean(settingEnabled, p.isEnabled(type)); - editor.putBoolean(settingVisible, p.isVisibleInTabs(type)); + for (PluginBase p : pluginList) { + PluginType type = p.getType(); + if (p.pluginDescription.alwaysEnabled && p.pluginDescription.alwayVisible) + continue; + if (p.pluginDescription.alwaysEnabled && p.pluginDescription.neverVisible) + continue; + savePref(p, type, true); + if (type == PluginType.PUMP) { + if (p instanceof ProfileInterface) { // Store state of optional Profile interface + savePref(p, PluginType.PROFILE, false); + } } } - editor.apply(); verifySelectionInCategories(); } } + private void savePref(PluginBase p, PluginType type, boolean storeVisible) { + String settingEnabled = "ConfigBuilder_" + type.name() + "_" + p.getClass().getSimpleName() + "_Enabled"; + SP.putBoolean(settingEnabled, p.isEnabled(type)); + log.debug("Storing: " + settingEnabled + ":" + p.isEnabled(type)); + if (storeVisible) { + String settingVisible = "ConfigBuilder_" + type.name() + "_" + p.getClass().getSimpleName() + "_Visible"; + SP.putBoolean(settingVisible, p.isFragmentVisible()); + log.debug("Storing: " + settingVisible + ":" + p.isFragmentVisible()); + } + } + private void loadSettings() { if (Config.logPrefsChange) log.debug("Loading stored settings"); - SharedPreferences SP = PreferenceManager.getDefaultSharedPreferences(MainApp.instance().getApplicationContext()); - for (int type = 1; type < PluginBase.LAST; type++) { - for (PluginBase p : pluginList) { - try { - String settingEnabled = "ConfigBuilder_" + type + "_" + p.getClass().getSimpleName() + "_Enabled"; - String settingVisible = "ConfigBuilder_" + type + "_" + p.getClass().getSimpleName() + "_Visible"; - if (SP.contains(settingEnabled)) - p.setFragmentEnabled(type, SP.getBoolean(settingEnabled, true)); - if (SP.contains(settingVisible)) - p.setFragmentVisible(type, SP.getBoolean(settingVisible, true) && SP.getBoolean(settingEnabled, true)); - } catch (Exception e) { - log.error("Unhandled exception", e); + for (PluginBase p : pluginList) { + PluginType type = p.getType(); + loadPref(p, type, true); + if (p.getType() == PluginType.PUMP) { + if (p instanceof ProfileInterface) { + loadPref(p, PluginType.PROFILE, false); } } } verifySelectionInCategories(); } + private void loadPref(PluginBase p, PluginType type, boolean loadVisible) { + String settingEnabled = "ConfigBuilder_" + type.name() + "_" + p.getClass().getSimpleName() + "_Enabled"; + if (SP.contains(settingEnabled)) + p.setPluginEnabled(type, SP.getBoolean(settingEnabled, false)); + else if (p.getType() == type && (p.pluginDescription.enableByDefault || p.pluginDescription.alwaysEnabled)) { + p.setPluginEnabled(type, true); + } + log.debug("Loaded: " + settingEnabled + ":" + p.isEnabled(type)); + if (loadVisible) { + String settingVisible = "ConfigBuilder_" + type.name() + "_" + p.getClass().getSimpleName() + "_Visible"; + if (SP.contains(settingVisible)) + p.setFragmentVisible(type, SP.getBoolean(settingVisible, false) && SP.getBoolean(settingEnabled, false)); + else if (p.getType() == type && p.pluginDescription.visibleByDefault) { + p.setFragmentVisible(type, true); + } + log.debug("Loaded: " + settingVisible + ":" + p.isFragmentVisible()); + } + } + + // Detect settings prior 1.60 + private void upgradeSettings() { + if (!SP.contains("ConfigBuilder_1_NSProfilePlugin_Enabled")) + return; + if (Config.logPrefsChange) + log.debug("Upgrading stored settings"); + for (PluginBase p : pluginList) { + log.debug("Processing " + p.getName()); + for (int type = 1; type < 11; type++) { + PluginType newType; + switch (type) { + case 1: + newType = PluginType.GENERAL; + break; + case 2: + newType = PluginType.TREATMENT; + break; + case 3: + newType = PluginType.SENSITIVITY; + break; + case 4: + newType = PluginType.PROFILE; + break; + case 5: + newType = PluginType.APS; + break; + case 6: + newType = PluginType.PUMP; + break; + case 7: + newType = PluginType.CONSTRAINTS; + break; + case 8: + newType = PluginType.LOOP; + break; + case 9: + newType = PluginType.BGSOURCE; + break; + case 10: + newType = PluginType.INSULIN; + break; + default: + newType = PluginType.GENERAL; + break; + } + String settingEnabled = "ConfigBuilder_" + type + "_" + p.getClass().getSimpleName() + "_Enabled"; + String settingVisible = "ConfigBuilder_" + type + "_" + p.getClass().getSimpleName() + "_Visible"; + if (SP.contains(settingEnabled)) + p.setPluginEnabled(newType, SP.getBoolean(settingEnabled, false)); + if (SP.contains(settingVisible)) + p.setFragmentVisible(newType, SP.getBoolean(settingVisible, false) && SP.getBoolean(settingEnabled, false)); + SP.remove(settingEnabled); + SP.remove(settingVisible); + if (newType == p.getType()) { + savePref(p, newType, true); + } else if (p.getType() == PluginType.PUMP && p instanceof ProfileInterface) { + savePref(p, PluginType.PROFILE, false); + } + } + } + } + public static CommandQueue getCommandQueue() { return commandQueue; } - public static BgSourceInterface getActiveBgSource() { + public BgSourceInterface getActiveBgSource() { return activeBgSource; } - public static ProfileInterface getActiveProfileInterface() { + public ProfileInterface getActiveProfileInterface() { return activeProfile; } @@ -209,10 +264,6 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr return activeAPS; } - public static LoopPlugin getActiveLoop() { - return activeLoop; - } - public static PumpInterface getActivePump() { return activePump; } @@ -224,16 +275,16 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr void logPluginStatus() { for (PluginBase p : pluginList) { log.debug(p.getName() + ":" + - (p.isEnabled(1) ? " GENERAL" : "") + - (p.isEnabled(2) ? " TREATMENT" : "") + - (p.isEnabled(3) ? " SENSITIVITY" : "") + - (p.isEnabled(4) ? " PROFILE" : "") + - (p.isEnabled(5) ? " APS" : "") + - (p.isEnabled(6) ? " PUMP" : "") + - (p.isEnabled(7) ? " CONSTRAINTS" : "") + - (p.isEnabled(8) ? " LOOP" : "") + - (p.isEnabled(9) ? " BGSOURCE" : "") + - (p.isEnabled(10) ? " INSULIN" : "") + (p.isEnabled(PluginType.GENERAL) ? " GENERAL" : "") + + (p.isEnabled(PluginType.TREATMENT) ? " TREATMENT" : "") + + (p.isEnabled(PluginType.SENSITIVITY) ? " SENSITIVITY" : "") + + (p.isEnabled(PluginType.PROFILE) ? " PROFILE" : "") + + (p.isEnabled(PluginType.APS) ? " APS" : "") + + (p.isEnabled(PluginType.PUMP) ? " PUMP" : "") + + (p.isEnabled(PluginType.CONSTRAINTS) ? " CONSTRAINTS" : "") + + (p.isEnabled(PluginType.LOOP) ? " LOOP" : "") + + (p.isEnabled(PluginType.BGSOURCE) ? " BGSOURCE" : "") + + (p.isEnabled(PluginType.INSULIN) ? " INSULIN" : "") ); } } @@ -241,145 +292,148 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr private void verifySelectionInCategories() { ArrayList pluginsInCategory; - // PluginBase.APS - pluginsInCategory = MainApp.getSpecificPluginsListByInterface(APSInterface.class); - activeAPS = (APSInterface) getTheOneEnabledInArray(pluginsInCategory, PluginBase.APS); - if (activeAPS != null) { - if (Config.logConfigBuilder) - log.debug("Selected APS interface: " + ((PluginBase) activeAPS).getName()); - for (PluginBase p : pluginsInCategory) { - if (!p.getName().equals(((PluginBase) activeAPS).getName())) { - p.setFragmentVisible(PluginBase.APS, false); - } - } + // PluginType.APS + activeAPS = this.determineActivePlugin(APSInterface.class, PluginType.APS); + + // PluginType.INSULIN + pluginsInCategory = MainApp.getSpecificPluginsList(PluginType.INSULIN); + activeInsulin = (InsulinInterface) getTheOneEnabledInArray(pluginsInCategory, PluginType.INSULIN); + if (activeInsulin == null) { + activeInsulin = InsulinOrefRapidActingPlugin.getPlugin(); + InsulinOrefRapidActingPlugin.getPlugin().setPluginEnabled(PluginType.INSULIN, true); + } + this.setFragmentVisiblities(((PluginBase) activeInsulin).getName(), pluginsInCategory, PluginType.INSULIN); + + // PluginType.SENSITIVITY + pluginsInCategory = MainApp.getSpecificPluginsList(PluginType.SENSITIVITY); + activeSensitivity = (SensitivityInterface) getTheOneEnabledInArray(pluginsInCategory, PluginType.SENSITIVITY); + if (activeSensitivity == null) { + activeSensitivity = SensitivityOref0Plugin.getPlugin(); + SensitivityOref0Plugin.getPlugin().setPluginEnabled(PluginType.SENSITIVITY, true); + } + this.setFragmentVisiblities(((PluginBase) activeSensitivity).getName(), pluginsInCategory, PluginType.SENSITIVITY); + + // PluginType.PROFILE + activeProfile = this.determineActivePlugin(ProfileInterface.class, PluginType.PROFILE); + + // PluginType.BGSOURCE + activeBgSource = this.determineActivePlugin(BgSourceInterface.class, PluginType.BGSOURCE); + + // PluginType.PUMP + pluginsInCategory = MainApp.getSpecificPluginsList(PluginType.PUMP); + activePump = (PumpInterface) getTheOneEnabledInArray(pluginsInCategory, PluginType.PUMP); + if (activePump == null) { + activePump = VirtualPumpPlugin.getPlugin(); + VirtualPumpPlugin.getPlugin().setPluginEnabled(PluginType.PUMP, true); + } + this.setFragmentVisiblities(((PluginBase) activePump).getName(), pluginsInCategory, PluginType.PUMP); + + // PluginType.LOOP + activeLoop = this.determineActivePlugin(PluginType.LOOP); + + // PluginType.TREATMENT + activeTreatments = this.determineActivePlugin(PluginType.TREATMENT); + } + + /** + * disables the visibility for all fragments of Plugins with the given PluginType + * which are not equally named to the Plugin implementing the given Plugin Interface. + * + * @param pluginInterface + * @param pluginType + * @param + * @return + */ + private T determineActivePlugin(Class pluginInterface, PluginType pluginType) { + ArrayList pluginsInCategory; + pluginsInCategory = MainApp.getSpecificPluginsListByInterface(pluginInterface); + + return this.determineActivePlugin(pluginsInCategory, pluginType); + } + + private T determineActivePlugin(PluginType pluginType) { + ArrayList pluginsInCategory; + pluginsInCategory = MainApp.getSpecificPluginsList(pluginType); + + return this.determineActivePlugin(pluginsInCategory, pluginType); + } + + /** + * disables the visibility for all fragments of Plugins in the given pluginsInCategory + * with the given PluginType which are not equally named to the Plugin implementing the + * given Plugin Interface. + *

+ * TODO we are casting an interface to PluginBase, which seems to be rather odd, since + * TODO the interface is not implementing PluginBase (this is just avoiding errors through + * TODO conventions. + * + * @param pluginsInCategory + * @param pluginType + * @param + * @return + */ + private T determineActivePlugin(ArrayList pluginsInCategory, + PluginType pluginType) { + T activePlugin = (T) getTheOneEnabledInArray(pluginsInCategory, pluginType); + + if (activePlugin != null) { + this.setFragmentVisiblities(((PluginBase) activePlugin).getName(), + pluginsInCategory, pluginType); } - // PluginBase.INSULIN - pluginsInCategory = MainApp.getSpecificPluginsListByInterface(InsulinInterface.class); - activeInsulin = (InsulinInterface) getTheOneEnabledInArray(pluginsInCategory, PluginBase.INSULIN); + return activePlugin; + } + + private void setFragmentVisiblities(String activePluginName, ArrayList pluginsInCategory, + PluginType pluginType) { if (Config.logConfigBuilder) - log.debug("Selected insulin interface: " + ((PluginBase) activeInsulin).getName()); + log.debug("Selected interface: " + activePluginName); for (PluginBase p : pluginsInCategory) { - if (!p.getName().equals(((PluginBase) activeInsulin).getName())) { - p.setFragmentVisible(PluginBase.INSULIN, false); - } - } - - // PluginBase.SENSITIVITY - pluginsInCategory = MainApp.getSpecificPluginsListByInterface(SensitivityInterface.class); - activeSensitivity = (SensitivityInterface) getTheOneEnabledInArray(pluginsInCategory, PluginBase.SENSITIVITY); - if (Config.logConfigBuilder) - log.debug("Selected sensitivity interface: " + ((PluginBase) activeSensitivity).getName()); - for (PluginBase p : pluginsInCategory) { - if (!p.getName().equals(((PluginBase) activeSensitivity).getName())) { - p.setFragmentVisible(PluginBase.SENSITIVITY, false); - } - } - - // PluginBase.PROFILE - pluginsInCategory = MainApp.getSpecificPluginsListByInterface(ProfileInterface.class); - activeProfile = (ProfileInterface) getTheOneEnabledInArray(pluginsInCategory, PluginBase.PROFILE); - if (Config.logConfigBuilder) - log.debug("Selected profile interface: " + ((PluginBase) activeProfile).getName()); - for (PluginBase p : pluginsInCategory) { - if (!p.getName().equals(((PluginBase) activeProfile).getName())) { - p.setFragmentVisible(PluginBase.PROFILE, false); - } - } - - // PluginBase.BGSOURCE - pluginsInCategory = MainApp.getSpecificPluginsListByInterface(BgSourceInterface.class); - activeBgSource = (BgSourceInterface) getTheOneEnabledInArray(pluginsInCategory, PluginBase.BGSOURCE); - if (Config.logConfigBuilder) - log.debug("Selected bgSource interface: " + ((PluginBase) activeBgSource).getName()); - for (PluginBase p : pluginsInCategory) { - if (!p.getName().equals(((PluginBase) activeBgSource).getName())) { - p.setFragmentVisible(PluginBase.BGSOURCE, false); - } - } - - // PluginBase.PUMP - pluginsInCategory = MainApp.getSpecificPluginsList(PluginBase.PUMP); - activePump = (PumpInterface) getTheOneEnabledInArray(pluginsInCategory, PluginBase.PUMP); - if (activePump == null) - activePump = VirtualPumpPlugin.getPlugin(); // for NSClient build - if (Config.logConfigBuilder) - log.debug("Selected pump interface: " + ((PluginBase) activePump).getName()); - for (PluginBase p : pluginsInCategory) { - if (!p.getName().equals(((PluginBase) activePump).getName())) { - p.setFragmentVisible(PluginBase.PUMP, false); - } - } - - // PluginBase.LOOP - pluginsInCategory = MainApp.getSpecificPluginsList(PluginBase.LOOP); - activeLoop = (LoopPlugin) getTheOneEnabledInArray(pluginsInCategory, PluginBase.LOOP); - if (activeLoop != null) { - if (Config.logConfigBuilder) - log.debug("Selected loop interface: " + activeLoop.getName()); - for (PluginBase p : pluginsInCategory) { - if (!p.getName().equals(activeLoop.getName())) { - p.setFragmentVisible(PluginBase.LOOP, false); - } - } - } - - // PluginBase.TREATMENT - pluginsInCategory = MainApp.getSpecificPluginsList(PluginBase.TREATMENT); - activeTreatments = (TreatmentsInterface) getTheOneEnabledInArray(pluginsInCategory, PluginBase.TREATMENT); - if (Config.logConfigBuilder) - log.debug("Selected treatment interface: " + ((PluginBase) activeTreatments).getName()); - for (PluginBase p : pluginsInCategory) { - if (!p.getName().equals(((PluginBase) activeTreatments).getName())) { - p.setFragmentVisible(PluginBase.TREATMENT, false); + if (!p.getName().equals(activePluginName)) { + p.setFragmentVisible(pluginType, false); } } } @Nullable - private PluginBase getTheOneEnabledInArray(ArrayList pluginsInCategory, int type) { + private PluginBase getTheOneEnabledInArray(ArrayList pluginsInCategory, PluginType type) { PluginBase found = null; for (PluginBase p : pluginsInCategory) { if (p.isEnabled(type) && found == null) { found = p; } else if (p.isEnabled(type)) { // set others disabled - p.setFragmentEnabled(type, false); + p.setPluginEnabled(type, false); } } // If none enabled, enable first one - if (found == null && pluginsInCategory.size() > 0) - found = pluginsInCategory.get(0); + //if (found == null && pluginsInCategory.size() > 0) + // found = pluginsInCategory.get(0); return found; } - /* - * Ex Pump interface - * - * Config builder return itself as a pump and check constraints before it passes command to pump driver - */ - - /** * expect absolute request and allow both absolute and percent response based on pump capabilities - * - * @param request - * @return - * true if command is going to be executed - * false if error */ + public void applyTBRRequest(APSResult request, Profile profile, Callback callback) { + if (!request.tempBasalRequested) { + if (callback != null) { + callback.result(new PumpEnactResult().enacted(false).success(true).comment(MainApp.gs(R.string.nochangerequested))).run(); + } + return; + } - public boolean applyAPSRequest(APSResult request, Callback callback) { PumpInterface pump = getActivePump(); - request.rate = applyBasalConstraints(request.rate); - PumpEnactResult result; + + request.rateConstraint = new Constraint<>(request.rate); + request.rate = MainApp.getConstraintChecker().applyBasalConstraints(request.rateConstraint, profile).value(); if (!pump.isInitialized()) { log.debug("applyAPSRequest: " + MainApp.sResources.getString(R.string.pumpNotInitialized)); if (callback != null) { callback.result(new PumpEnactResult().comment(MainApp.sResources.getString(R.string.pumpNotInitialized)).enacted(false).success(false)).run(); } - return false; + return; } if (pump.isSuspended()) { @@ -387,347 +441,110 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr if (callback != null) { callback.result(new PumpEnactResult().comment(MainApp.sResources.getString(R.string.pumpsuspended)).enacted(false).success(false)).run(); } - return false; + return; } if (Config.logCongigBuilderActions) log.debug("applyAPSRequest: " + request.toString()); + + long now = System.currentTimeMillis(); + TemporaryBasal activeTemp = activeTreatments.getTempBasalFromHistory(now); if ((request.rate == 0 && request.duration == 0) || Math.abs(request.rate - pump.getBaseBasalRate()) < pump.getPumpDescription().basalStep) { - if (isTempBasalInProgress()) { + if (activeTemp != null) { if (Config.logCongigBuilderActions) log.debug("applyAPSRequest: cancelTempBasal()"); getCommandQueue().cancelTempBasal(false, callback); - return true; } else { if (Config.logCongigBuilderActions) log.debug("applyAPSRequest: Basal set correctly"); if (callback != null) { - callback.result(new PumpEnactResult().absolute(request.rate).duration(0).enacted(false).success(true).comment("Basal set correctly")).run(); + callback.result(new PumpEnactResult().absolute(request.rate).duration(0) + .enacted(false).success(true).comment(MainApp.gs(R.string.basal_set_correctly))).run(); } - return false; } - } else if (isTempBasalInProgress() - && getTempBasalRemainingMinutesFromHistory() > 5 - && Math.abs(request.rate - getTempBasalAbsoluteRateHistory()) < pump.getPumpDescription().basalStep) { + } else if (activeTemp != null + && activeTemp.getPlannedRemainingMinutes() > 5 + && request.duration - activeTemp.getPlannedRemainingMinutes() < 30 + && Math.abs(request.rate - activeTemp.tempBasalConvertedToAbsolute(now, profile)) < pump.getPumpDescription().basalStep) { if (Config.logCongigBuilderActions) log.debug("applyAPSRequest: Temp basal set correctly"); if (callback != null) { - callback.result(new PumpEnactResult().absolute(getTempBasalAbsoluteRateHistory()).duration(getTempBasalFromHistory(System.currentTimeMillis()).getPlannedRemainingMinutes()).enacted(false).success(true).comment("Temp basal set correctly")).run(); + callback.result(new PumpEnactResult().absolute(activeTemp.tempBasalConvertedToAbsolute(now, profile)) + .enacted(false).success(true).duration(activeTemp.getPlannedRemainingMinutes()) + .comment(MainApp.gs(R.string.let_temp_basal_run))).run(); } - return false; } else { if (Config.logCongigBuilderActions) log.debug("applyAPSRequest: setTempBasalAbsolute()"); - getCommandQueue().tempBasalAbsolute(request.rate, request.duration, false, callback); - return true; + getCommandQueue().tempBasalAbsolute(request.rate, request.duration, false, profile, callback); } } - - /** - * Constraints interface - **/ - @Override - public boolean isLoopEnabled() { - boolean result = true; - - ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); - for (PluginBase p : constraintsPlugins) { - ConstraintsInterface constrain = (ConstraintsInterface) p; - if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue; - result = result && constrain.isLoopEnabled(); + public void applySMBRequest(APSResult request, Callback callback) { + if (!request.bolusRequested) { + return; } - return result; - } - @Override - public boolean isClosedModeEnabled() { - boolean result = true; - - ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); - for (PluginBase p : constraintsPlugins) { - ConstraintsInterface constrain = (ConstraintsInterface) p; - if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue; - result = result && constrain.isClosedModeEnabled(); + long lastBolusTime = activeTreatments.getLastBolusTime(); + if (lastBolusTime != 0 && lastBolusTime + 3 * 60 * 1000 > System.currentTimeMillis()) { + log.debug("SMB requested but still in 3 min interval"); + if (callback != null) { + callback.result(new PumpEnactResult() + .comment(MainApp.gs(R.string.smb_frequency_exceeded)) + .enacted(false).success(false)).run(); + } + return; } - return result; - } - @Override - public boolean isAutosensModeEnabled() { - boolean result = true; + PumpInterface pump = getActivePump(); - ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); - for (PluginBase p : constraintsPlugins) { - ConstraintsInterface constrain = (ConstraintsInterface) p; - if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue; - result = result && constrain.isAutosensModeEnabled(); + if (!pump.isInitialized()) { + log.debug("applySMBRequest: " + MainApp.sResources.getString(R.string.pumpNotInitialized)); + if (callback != null) { + callback.result(new PumpEnactResult().comment(MainApp.sResources.getString(R.string.pumpNotInitialized)).enacted(false).success(false)).run(); + } + return; } - return result; - } - @Override - public boolean isAMAModeEnabled() { - boolean result = SP.getBoolean("openapsama_useautosens", false); - - ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); - for (PluginBase p : constraintsPlugins) { - ConstraintsInterface constrain = (ConstraintsInterface) p; - if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue; - result = result && constrain.isAMAModeEnabled(); + if (pump.isSuspended()) { + log.debug("applySMBRequest: " + MainApp.sResources.getString(R.string.pumpsuspended)); + if (callback != null) { + callback.result(new PumpEnactResult().comment(MainApp.sResources.getString(R.string.pumpsuspended)).enacted(false).success(false)).run(); + } + return; } - return result; + + if (Config.logCongigBuilderActions) + log.debug("applySMBRequest: " + request.toString()); + + // deliver SMB + DetailedBolusInfo detailedBolusInfo = new DetailedBolusInfo(); + detailedBolusInfo.eventType = CareportalEvent.CORRECTIONBOLUS; + detailedBolusInfo.insulin = request.smb; + detailedBolusInfo.isSMB = true; + detailedBolusInfo.source = Source.USER; + detailedBolusInfo.deliverAt = request.deliverAt; + if (Config.logCongigBuilderActions) + log.debug("applyAPSRequest: bolus()"); + getCommandQueue().bolus(detailedBolusInfo, callback); } - @Override - public boolean isSMBModeEnabled() { - boolean result = true; // TODO update for SMB // SP.getBoolean("openapsama_useautosens", false); - - ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); - for (PluginBase p : constraintsPlugins) { - ConstraintsInterface constrain = (ConstraintsInterface) p; - if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue; - result = result && constrain.isSMBModeEnabled(); - } - return result; - } - - @Override - public Double applyBasalConstraints(Double absoluteRate) { - Double rateAfterConstrain = absoluteRate; - ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); - for (PluginBase p : constraintsPlugins) { - ConstraintsInterface constrain = (ConstraintsInterface) p; - if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue; - rateAfterConstrain = Math.min(constrain.applyBasalConstraints(absoluteRate), rateAfterConstrain); - } - return rateAfterConstrain; - } - - @Override - public Integer applyBasalConstraints(Integer percentRate) { - Integer rateAfterConstrain = percentRate; - ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); - for (PluginBase p : constraintsPlugins) { - ConstraintsInterface constrain = (ConstraintsInterface) p; - if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue; - rateAfterConstrain = Math.min(constrain.applyBasalConstraints(percentRate), rateAfterConstrain); - } - return rateAfterConstrain; - } - - @Override - public Double applyBolusConstraints(Double insulin) { - Double insulinAfterConstrain = insulin; - ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); - for (PluginBase p : constraintsPlugins) { - ConstraintsInterface constrain = (ConstraintsInterface) p; - if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue; - insulinAfterConstrain = Math.min(constrain.applyBolusConstraints(insulin), insulinAfterConstrain); - } - return insulinAfterConstrain; - } - - @Override - public Integer applyCarbsConstraints(Integer carbs) { - Integer carbsAfterConstrain = carbs; - ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); - for (PluginBase p : constraintsPlugins) { - ConstraintsInterface constrain = (ConstraintsInterface) p; - if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue; - carbsAfterConstrain = Math.min(constrain.applyCarbsConstraints(carbs), carbsAfterConstrain); - } - return carbsAfterConstrain; - } - - @Override - public Double applyMaxIOBConstraints(Double maxIob) { - Double maxIobAfterConstrain = maxIob; - ArrayList constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class); - for (PluginBase p : constraintsPlugins) { - ConstraintsInterface constrain = (ConstraintsInterface) p; - if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue; - maxIobAfterConstrain = Math.min(constrain.applyMaxIOBConstraints(maxIob), maxIobAfterConstrain); - } - return maxIobAfterConstrain; - } - - // ****** Treatments interface ***** - @Override - public void updateTotalIOBTreatments() { - activeTreatments.updateTotalIOBTreatments(); - } - - @Override - public void updateTotalIOBTempBasals() { - activeTreatments.updateTotalIOBTempBasals(); - } - - @Override - public IobTotal getLastCalculationTreatments() { - return activeTreatments.getLastCalculationTreatments(); - } - - @Override - public IobTotal getCalculationToTimeTreatments(long time) { - return activeTreatments.getCalculationToTimeTreatments(time); - } - - @Override - public IobTotal getLastCalculationTempBasals() { - return activeTreatments.getLastCalculationTempBasals(); - } - - @Override - public IobTotal getCalculationToTimeTempBasals(long time) { - return activeTreatments.getCalculationToTimeTempBasals(time); - } - - @Override - public MealData getMealData() { - return activeTreatments.getMealData(); - } - - @Override - public List getTreatmentsFromHistory() { - return activeTreatments.getTreatmentsFromHistory(); - } - - @Override - public List getTreatments5MinBackFromHistory(long time) { - return activeTreatments.getTreatments5MinBackFromHistory(time); - } - - @Override - public boolean isInHistoryRealTempBasalInProgress() { - return activeTreatments.isInHistoryRealTempBasalInProgress(); - } - - @Override - @Nullable - public TemporaryBasal getRealTempBasalFromHistory(long time) { - return activeTreatments.getRealTempBasalFromHistory(time); - } - - @Override - public boolean isTempBasalInProgress() { - return activeTreatments != null && activeTreatments.isTempBasalInProgress(); - } - - @Override - @Nullable - public TemporaryBasal getTempBasalFromHistory(long time) { - return activeTreatments != null ? activeTreatments.getTempBasalFromHistory(time) : null; - } - - @Override - public double getTempBasalAbsoluteRateHistory() { - return activeTreatments.getTempBasalAbsoluteRateHistory(); - } - - @Override - public double getTempBasalRemainingMinutesFromHistory() { - return activeTreatments.getTempBasalRemainingMinutesFromHistory(); - } - - @Override - public Intervals getTemporaryBasalsFromHistory() { - return activeTreatments.getTemporaryBasalsFromHistory(); - } - - @Override - public boolean addToHistoryTempBasal(TemporaryBasal tempBasal) { - boolean newRecordCreated = activeTreatments.addToHistoryTempBasal(tempBasal); - if (newRecordCreated) { - if (tempBasal.durationInMinutes == 0) - NSUpload.uploadTempBasalEnd(tempBasal.date, false, tempBasal.pumpId); - else if (tempBasal.isAbsolute) - NSUpload.uploadTempBasalStartAbsolute(tempBasal, null); - else - NSUpload.uploadTempBasalStartPercent(tempBasal); - } - return newRecordCreated; - } - - @Override - public boolean isInHistoryExtendedBoluslInProgress() { - return activeTreatments.isInHistoryExtendedBoluslInProgress(); - } - - @Override - @Nullable - public ExtendedBolus getExtendedBolusFromHistory(long time) { - return activeTreatments.getExtendedBolusFromHistory(time); - } - - @Override - public boolean addToHistoryExtendedBolus(ExtendedBolus extendedBolus) { - boolean newRecordCreated = activeTreatments.addToHistoryExtendedBolus(extendedBolus); - if (newRecordCreated) { - if (extendedBolus.durationInMinutes == 0) { - if (activePump.isFakingTempsByExtendedBoluses()) - NSUpload.uploadTempBasalEnd(extendedBolus.date, true, extendedBolus.pumpId); - else - NSUpload.uploadExtendedBolusEnd(extendedBolus.date, extendedBolus.pumpId); - } else if (activePump.isFakingTempsByExtendedBoluses()) - NSUpload.uploadTempBasalStartAbsolute(new TemporaryBasal(extendedBolus), extendedBolus.insulin); - else - NSUpload.uploadExtendedBolus(extendedBolus); - } - return newRecordCreated; - } - - @Override - public Intervals getExtendedBolusesFromHistory() { - return activeTreatments.getExtendedBolusesFromHistory(); - } - - @Override - // return true if new record is created - public boolean addToHistoryTreatment(DetailedBolusInfo detailedBolusInfo) { - boolean newRecordCreated = activeTreatments.addToHistoryTreatment(detailedBolusInfo); - if (newRecordCreated && detailedBolusInfo.isValid) - NSUpload.uploadBolusWizardRecord(detailedBolusInfo); - return newRecordCreated; - } - - @Override - @Nullable - public TempTarget getTempTargetFromHistory() { - return activeTreatments.getTempTargetFromHistory(System.currentTimeMillis()); - } - - @Override - @Nullable - public TempTarget getTempTargetFromHistory(long time) { - return activeTreatments.getTempTargetFromHistory(time); - } - - @Override - public Intervals getTempTargetsFromHistory() { - return activeTreatments.getTempTargetsFromHistory(); - } - - @Override - @Nullable - public ProfileSwitch getProfileSwitchFromHistory(long time) { - return activeTreatments.getProfileSwitchFromHistory(time); - } - - @Override - public ProfileIntervals getProfileSwitchesFromHistory() { - return activeTreatments.getProfileSwitchesFromHistory(); - } - - @Override - public void addToHistoryProfileSwitch(ProfileSwitch profileSwitch) { - activeTreatments.addToHistoryProfileSwitch(profileSwitch); - NSUpload.uploadProfileSwitch(profileSwitch); - } - - @Override - public long oldestDataAvailable() { - return activeTreatments.oldestDataAvailable(); + @Subscribe + public void onProfileSwitch(EventProfileSwitchChange ignored) { + getCommandQueue().setProfile(getProfile(), new Callback() { + @Override + public void run() { + if (!result.success) { + Intent i = new Intent(MainApp.instance(), ErrorHelperActivity.class); + i.putExtra("soundid", R.raw.boluserror); + i.putExtra("status", result.comment); + i.putExtra("title", MainApp.sResources.getString(R.string.failedupdatebasalprofile)); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + MainApp.instance().startActivity(i); + } + MainApp.bus().post(new EventNewBasalProfile()); + } + }); } public String getProfileName() { @@ -743,22 +560,24 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr } public String getProfileName(long time, boolean customized) { - ProfileSwitch profileSwitch = getProfileSwitchFromHistory(time); + ProfileSwitch profileSwitch = activeTreatments.getProfileSwitchFromHistory(time); if (profileSwitch != null) { if (profileSwitch.profileJson != null) { return customized ? profileSwitch.getCustomizedName() : profileSwitch.profileName; } else { - Profile profile = activeProfile.getProfile().getSpecificProfile(profileSwitch.profileName); - if (profile != null) - return profileSwitch.profileName; + ProfileStore profileStore = activeProfile.getProfile(); + if (profileStore != null) { + Profile profile = profileStore.getSpecificProfile(profileSwitch.profileName); + if (profile != null) + return profileSwitch.profileName; + } } } - // Unable to determine profile, failover to default - String defaultProfile = activeProfile.getProfile().getDefaultProfileName(); - if (defaultProfile != null) - return defaultProfile; - // If default from plugin fails .... create empty - return "Default"; + return MainApp.gs(R.string.noprofileselected); + } + + public boolean isProfileValid(String from) { + return getProfile() != null && getProfile().isValid(from); } @Nullable @@ -773,42 +592,66 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr @Nullable public Profile getProfile(long time) { - if (activeTreatments == null) + if (activeTreatments == null) { + log.debug("getProfile activeTreatments == null: returning null"); return null; //app not initialized + } //log.debug("Profile for: " + new Date(time).toLocaleString() + " : " + getProfileName(time)); - boolean ignoreProfileSwitchEvents = SP.getBoolean(R.string.key_do_not_track_profile_switch, false); - if (!ignoreProfileSwitchEvents) { - ProfileSwitch profileSwitch = getProfileSwitchFromHistory(time); - if (profileSwitch != null) { - if (profileSwitch.profileJson != null) { - return profileSwitch.getProfileObject(); - } else if (activeProfile.getProfile() != null) { - Profile profile = activeProfile.getProfile().getSpecificProfile(profileSwitch.profileName); - if (profile != null) - return profile; - } + ProfileSwitch profileSwitch = activeTreatments.getProfileSwitchFromHistory(time); + if (profileSwitch != null) { + if (profileSwitch.profileJson != null) { + return profileSwitch.getProfileObject(); + } else if (activeProfile.getProfile() != null) { + Profile profile = activeProfile.getProfile().getSpecificProfile(profileSwitch.profileName); + if (profile != null) + return profile; } - // Unable to determine profile, failover to default - if (activeProfile.getProfile() == null) - return null; //app not initialized } - Profile defaultProfile = activeProfile.getProfile().getDefaultProfile(); - if (defaultProfile != null) - return defaultProfile; - // If default from plugin fails .... create empty - try { - Notification noisf = new Notification(Notification.ISF_MISSING, MainApp.sResources.getString(R.string.isfmissing), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(noisf)); - Notification noic = new Notification(Notification.IC_MISSING, MainApp.sResources.getString(R.string.icmissing), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(noic)); - Notification nobasal = new Notification(Notification.BASAL_MISSING, MainApp.sResources.getString(R.string.basalmissing), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(nobasal)); - Notification notarget = new Notification(Notification.TARGET_MISSING, MainApp.sResources.getString(R.string.targetmissing), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notarget)); - return new Profile(new JSONObject("{\"dia\":\"3\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"20\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"20\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"0.1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"6\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"8\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}}"), 100, 0); - } catch (JSONException e) { - log.error("Unhandled exception", e); + if (activeTreatments.getProfileSwitchesFromHistory().size() > 0) { + FabricPrivacy.getInstance().logCustom(new CustomEvent("CatchedError") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION) + .putCustomAttribute("time", time) + .putCustomAttribute("getProfileSwitchesFromHistory", activeTreatments.getProfileSwitchesFromHistory().toString()) + ); } + log.debug("getProfile at the end: returning null"); return null; } + + public void disconnectPump(int durationInMinutes, Profile profile) { + LoopPlugin.getPlugin().disconnectTo(System.currentTimeMillis() + durationInMinutes * 60 * 1000L); + getCommandQueue().tempBasalPercent(0, durationInMinutes, true, profile, new Callback() { + @Override + public void run() { + if (!result.success) { + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.tempbasaldeliveryerror)); + } + } + }); + if (getActivePump().getPumpDescription().isExtendedBolusCapable && activeTreatments.isInHistoryExtendedBoluslInProgress()) { + getCommandQueue().cancelExtended(new Callback() { + @Override + public void run() { + if (!result.success) { + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.extendedbolusdeliveryerror)); + } + } + }); + } + NSUpload.uploadOpenAPSOffline(durationInMinutes); + } + + public void suspendLoop(int durationInMinutes) { + LoopPlugin.getPlugin().suspendTo(System.currentTimeMillis() + durationInMinutes * 60 * 1000); + getCommandQueue().cancelTempBasal(true, new Callback() { + @Override + public void run() { + if (!result.success) { + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.tempbasaldeliveryerror)); + } + } + }); + NSUpload.uploadOpenAPSOffline(durationInMinutes); + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ConstraintsObjectives/ObjectivesFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/ConstraintsObjectives/ObjectivesFragment.java index e4eef9ff2f..688c741329 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ConstraintsObjectives/ObjectivesFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ConstraintsObjectives/ObjectivesFragment.java @@ -15,7 +15,6 @@ import android.widget.CheckBox; import android.widget.LinearLayout; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,8 +24,10 @@ import java.util.List; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.Common.SubscriberFragment; +import info.nightscout.utils.FabricPrivacy; -public class ObjectivesFragment extends Fragment { +public class ObjectivesFragment extends SubscriberFragment { private static Logger log = LoggerFactory.getLogger(ObjectivesFragment.class); RecyclerView recyclerView; @@ -84,37 +85,52 @@ public class ObjectivesFragment extends Fragment { } }); - Long now = System.currentTimeMillis(); - if (position > 0 && objectives.get(position - 1).accomplished.getTime() == 0) { - // Phase 0: previous not completed - holder.startedLayout.setVisibility(View.GONE); - holder.durationLayout.setVisibility(View.GONE); - holder.progressLayout.setVisibility(View.GONE); - holder.verifyLayout.setVisibility(View.GONE); - } else if (o.started.getTime() == 0) { - // Phase 1: not started - holder.durationLayout.setVisibility(View.GONE); - holder.progressLayout.setVisibility(View.GONE); - holder.verifyLayout.setVisibility(View.GONE); - holder.started.setVisibility(View.GONE); - } else if (o.started.getTime() > 0 && !enableFake.isChecked() && o.accomplished.getTime() == 0 && !(o.started.getTime() + o.durationInDays * 24 * 60 * 60 * 1000 < now && requirementsMet.done)) { - // Phase 2: started, waiting for duration and met requirements - holder.startButton.setEnabled(false); - holder.verifyLayout.setVisibility(View.GONE); - } else if (o.accomplished.getTime() == 0) { - // Phase 3: started, after duration, requirements met - holder.startButton.setEnabled(false); - holder.accomplished.setVisibility(View.INVISIBLE); - } else { - // Phase 4: verified - holder.gateLayout.setVisibility(View.GONE); - holder.startedLayout.setVisibility(View.GONE); - holder.durationLayout.setVisibility(View.GONE); - holder.progressLayout.setVisibility(View.GONE); - holder.verifyButton.setVisibility(View.INVISIBLE); + long prevObjectiveAccomplishedTime = position > 0 ? + objectives.get(position - 1).accomplished.getTime() : -1; + + int phase = modifyVisibility(position, prevObjectiveAccomplishedTime, + o.started.getTime(), o.durationInDays, + o.accomplished.getTime(), requirementsMet.done, enableFake.isChecked()); + + switch (phase) { + case 0: + // Phase 0: previous not completed + holder.startedLayout.setVisibility(View.GONE); + holder.durationLayout.setVisibility(View.GONE); + holder.progressLayout.setVisibility(View.GONE); + holder.verifyLayout.setVisibility(View.GONE); + break; + case 1: + // Phase 1: not started + holder.durationLayout.setVisibility(View.GONE); + holder.progressLayout.setVisibility(View.GONE); + holder.verifyLayout.setVisibility(View.GONE); + holder.started.setVisibility(View.GONE); + break; + case 2: + // Phase 2: started, waiting for duration and met requirements + holder.startButton.setEnabled(false); + holder.verifyLayout.setVisibility(View.GONE); + break; + case 3: + // Phase 3: started, after duration, requirements met + holder.startButton.setEnabled(false); + holder.accomplished.setVisibility(View.INVISIBLE); + break; + case 4: + // Phase 4: verified + holder.gateLayout.setVisibility(View.GONE); + holder.startedLayout.setVisibility(View.GONE); + holder.durationLayout.setVisibility(View.GONE); + holder.progressLayout.setVisibility(View.GONE); + holder.verifyButton.setVisibility(View.INVISIBLE); + break; + default: + // should not happen } } + @Override public int getItemCount() { return objectives.size(); @@ -163,6 +179,40 @@ public class ObjectivesFragment extends Fragment { } } + /** + * returns an int, which represents the phase the current objective is at. + * + * this is mainly used for unit-testing the conditions + * + * @param currentPosition + * @param prevObjectiveAccomplishedTime + * @param objectiveStartedTime + * @param durationInDays + * @param objectiveAccomplishedTime + * @param requirementsMet + * @return + */ + public int modifyVisibility(int currentPosition, + long prevObjectiveAccomplishedTime, + long objectiveStartedTime, int durationInDays, + long objectiveAccomplishedTime, boolean requirementsMet, + boolean enableFakeValue) { + Long now = System.currentTimeMillis(); + if (currentPosition > 0 && prevObjectiveAccomplishedTime == 0) { + return 0; + } else if (objectiveStartedTime == 0) { + return 1; + } else if (objectiveStartedTime > 0 && !enableFakeValue + && objectiveAccomplishedTime == 0 + && !(objectiveStartedTime + durationInDays * 24 * 60 * 60 * 1000 >= now && requirementsMet)) { + return 2; + } else if (objectiveAccomplishedTime == 0) { + return 3; + } else { + return 4; + } + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -204,26 +254,25 @@ public class ObjectivesFragment extends Fragment { ObjectivesPlugin.objectives.get(3).gate = MainApp.sResources.getString(R.string.objectives_3_gate); ObjectivesPlugin.objectives.get(4).gate = MainApp.sResources.getString(R.string.objectives_4_gate); ObjectivesPlugin.objectives.get(5).gate = MainApp.sResources.getString(R.string.objectives_5_gate); + updateGUI(); return view; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } return null; } - void updateGUI() { + @Override + public void updateGUI() { Activity activity = getActivity(); if (activity != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - RecyclerViewAdapter adapter = new RecyclerViewAdapter(ObjectivesPlugin.objectives); - recyclerView.setAdapter(adapter); - } + activity.runOnUiThread(() -> { + RecyclerViewAdapter adapter = new RecyclerViewAdapter(ObjectivesPlugin.objectives); + recyclerView.setAdapter(adapter); }); } -} +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ConstraintsObjectives/ObjectivesPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/ConstraintsObjectives/ObjectivesPlugin.java index 3b0891680d..5a899bebe5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ConstraintsObjectives/ObjectivesPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ConstraintsObjectives/ObjectivesPlugin.java @@ -10,25 +10,30 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.db.DatabaseHelper; import info.nightscout.androidaps.interfaces.APSInterface; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.ConstraintsInterface; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.ConstraintsSafety.SafetyPlugin; import info.nightscout.androidaps.plugins.Loop.LoopPlugin; -import info.nightscout.androidaps.plugins.NSClientInternal.NSClientInternalPlugin; +import info.nightscout.androidaps.plugins.NSClientInternal.NSClientPlugin; import info.nightscout.androidaps.plugins.PumpVirtual.VirtualPumpPlugin; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; +import info.nightscout.utils.DateUtil; import info.nightscout.utils.SP; /** * Created by mike on 05.08.2016. */ -public class ObjectivesPlugin implements PluginBase, ConstraintsInterface { +public class ObjectivesPlugin extends PluginBase implements ConstraintsInterface { private static Logger log = LoggerFactory.getLogger(ObjectivesPlugin.class); private static ObjectivesPlugin objectivesPlugin; @@ -42,80 +47,26 @@ public class ObjectivesPlugin implements PluginBase, ConstraintsInterface { public static List objectives; - private boolean fragmentVisible = true; - private ObjectivesPlugin() { + super(new PluginDescription() + .mainType(PluginType.CONSTRAINTS) + .fragmentClass(ObjectivesFragment.class.getName()) + .alwaysEnabled(!Config.NSCLIENT && !Config.G5UPLOADER) + .showInList(!Config.NSCLIENT && !Config.G5UPLOADER) + .pluginName(R.string.objectives) + .shortName(R.string.objectives_shortname) + ); initializeData(); loadProgress(); - MainApp.bus().register(this); } @Override - public String getFragmentClass() { - return ObjectivesFragment.class.getName(); + public boolean specialEnableCondition() { + PumpInterface pump = ConfigBuilderPlugin.getActivePump(); + return pump == null || pump.getPumpDescription().isTempBasalCapable; } - @Override - public int getType() { - return PluginBase.CONSTRAINTS; - } - - @Override - public String getName() { - return MainApp.instance().getString(R.string.objectives); - } - - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.objectives_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == CONSTRAINTS && ConfigBuilderPlugin.getActivePump().getPumpDescription().isTempBasalCapable; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == CONSTRAINTS && fragmentVisible && !Config.NSCLIENT && !Config.G5UPLOADER; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == CONSTRAINTS) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return -1; - } - - class Objective { + public class Objective { Integer num; String objective; String gate; @@ -131,6 +82,18 @@ public class ObjectivesPlugin implements PluginBase, ConstraintsInterface { this.durationInDays = durationInDays; this.accomplished = accomplished; } + + public void setStarted(Date started) { + this.started = started; + } + + boolean isStarted() { + return started.getTime() > 0; + } + + boolean isFinished() { + return accomplished.getTime() != 0; + } } // Objective 0 @@ -158,37 +121,41 @@ public class ObjectivesPlugin implements PluginBase, ConstraintsInterface { RequirementResult requirementsMet(Integer objNum) { switch (objNum) { case 0: - boolean isVirtualPump = VirtualPumpPlugin.getPlugin().isEnabled(PluginBase.PUMP); + boolean isVirtualPump = VirtualPumpPlugin.getPlugin().isEnabled(PluginType.PUMP); boolean vpUploadEnabled = SP.getBoolean("virtualpump_uploadstatus", false); boolean vpUploadNeeded = !isVirtualPump || vpUploadEnabled; boolean hasBGData = DatabaseHelper.lastBg() != null; boolean apsEnabled = false; APSInterface usedAPS = ConfigBuilderPlugin.getActiveAPS(); - if (usedAPS != null && ((PluginBase) usedAPS).isEnabled(PluginBase.APS)) + if (usedAPS != null && ((PluginBase) usedAPS).isEnabled(PluginType.APS)) apsEnabled = true; - return new RequirementResult(hasBGData && bgIsAvailableInNS && pumpStatusIsAvailableInNS && NSClientInternalPlugin.getPlugin().hasWritePermission() && LoopPlugin.getPlugin().isEnabled(PluginBase.LOOP) && apsEnabled && vpUploadNeeded, - MainApp.sResources.getString(R.string.objectives_bgavailableinns) + ": " + yesOrNo(bgIsAvailableInNS) - + "\n" + MainApp.sResources.getString(R.string.nsclienthaswritepermission) + ": " + yesOrNo(NSClientInternalPlugin.getPlugin().hasWritePermission()) - + (isVirtualPump ? "\n" + MainApp.sResources.getString(R.string.virtualpump_uploadstatus_title) + ": " + yesOrNo(vpUploadEnabled) : "") - + "\n" + MainApp.sResources.getString(R.string.objectives_pumpstatusavailableinns) + ": " + yesOrNo(pumpStatusIsAvailableInNS) - + "\n" + MainApp.sResources.getString(R.string.hasbgdata) + ": " + yesOrNo(hasBGData) - + "\n" + MainApp.sResources.getString(R.string.loopenabled) + ": " + yesOrNo(LoopPlugin.getPlugin().isEnabled(PluginBase.LOOP)) - + "\n" + MainApp.sResources.getString(R.string.apsselected) + ": " + yesOrNo(apsEnabled) + boolean profileSwitchExists = TreatmentsPlugin.getPlugin().getProfileSwitchFromHistory(DateUtil.now()) != null; + + return new RequirementResult(hasBGData && bgIsAvailableInNS && pumpStatusIsAvailableInNS && NSClientPlugin.getPlugin().hasWritePermission() && LoopPlugin.getPlugin().isEnabled(PluginType.LOOP) && apsEnabled && vpUploadNeeded && profileSwitchExists, + MainApp.gs(R.string.objectives_bgavailableinns) + ": " + yesOrNo(bgIsAvailableInNS) + + "\n" + MainApp.gs(R.string.nsclienthaswritepermission) + ": " + yesOrNo(NSClientPlugin.getPlugin().hasWritePermission()) + + (isVirtualPump ? "\n" + MainApp.gs(R.string.virtualpump_uploadstatus_title) + ": " + yesOrNo(vpUploadEnabled) : "") + + "\n" + MainApp.gs(R.string.objectives_pumpstatusavailableinns) + ": " + yesOrNo(pumpStatusIsAvailableInNS) + + "\n" + MainApp.gs(R.string.hasbgdata) + ": " + yesOrNo(hasBGData) + + "\n" + MainApp.gs(R.string.loopenabled) + ": " + yesOrNo(LoopPlugin.getPlugin().isEnabled(PluginType.LOOP)) + + "\n" + MainApp.gs(R.string.apsselected) + ": " + yesOrNo(apsEnabled) + + "\n" + MainApp.gs(R.string.activate_profile) + ": " + yesOrNo(profileSwitchExists) ); case 1: return new RequirementResult(manualEnacts >= manualEnactsNeeded, - MainApp.sResources.getString(R.string.objectives_manualenacts) + ": " + manualEnacts + "/" + manualEnactsNeeded); + MainApp.gs(R.string.objectives_manualenacts) + ": " + manualEnacts + "/" + manualEnactsNeeded); case 2: return new RequirementResult(true, ""); case 3: - boolean closedModeEnabled = SafetyPlugin.getPlugin().isClosedModeEnabled(); - return new RequirementResult(closedModeEnabled, MainApp.sResources.getString(R.string.closedmodeenabled) + ": " + yesOrNo(closedModeEnabled)); + Constraint closedLoopEnabled = new Constraint<>(true); + SafetyPlugin.getPlugin().isClosedLoopAllowed(closedLoopEnabled); + return new RequirementResult(closedLoopEnabled.value(), MainApp.gs(R.string.closedmodeenabled) + ": " + yesOrNo(closedLoopEnabled.value())); case 4: - double maxIOB = MainApp.getConfigBuilder().applyMaxIOBConstraints(1000d); + double maxIOB = MainApp.getConstraintChecker().getMaxIOBAllowed().value(); boolean maxIobSet = maxIOB > 0; - return new RequirementResult(maxIobSet, MainApp.sResources.getString(R.string.maxiobset) + ": " + yesOrNo(maxIobSet)); + return new RequirementResult(maxIobSet, MainApp.gs(R.string.maxiobset) + ": " + yesOrNo(maxIobSet)); default: return new RequirementResult(true, ""); } @@ -202,49 +169,49 @@ public class ObjectivesPlugin implements PluginBase, ConstraintsInterface { objectives = new ArrayList<>(); objectives.add(new Objective(0, - MainApp.sResources.getString(R.string.objectives_0_objective), - MainApp.sResources.getString(R.string.objectives_0_gate), + MainApp.gs(R.string.objectives_0_objective), + MainApp.gs(R.string.objectives_0_gate), new Date(0), 0, // 0 day new Date(0))); objectives.add(new Objective(1, - MainApp.sResources.getString(R.string.objectives_1_objective), - MainApp.sResources.getString(R.string.objectives_1_gate), + MainApp.gs(R.string.objectives_1_objective), + MainApp.gs(R.string.objectives_1_gate), new Date(0), 7, // 7 days new Date(0))); objectives.add(new Objective(2, - MainApp.sResources.getString(R.string.objectives_2_objective), - MainApp.sResources.getString(R.string.objectives_2_gate), + MainApp.gs(R.string.objectives_2_objective), + MainApp.gs(R.string.objectives_2_gate), new Date(0), 0, // 0 days new Date(0))); objectives.add(new Objective(3, - MainApp.sResources.getString(R.string.objectives_3_objective), - MainApp.sResources.getString(R.string.objectives_3_gate), + MainApp.gs(R.string.objectives_3_objective), + MainApp.gs(R.string.objectives_3_gate), new Date(0), 5, // 5 days new Date(0))); objectives.add(new Objective(4, - MainApp.sResources.getString(R.string.objectives_4_objective), - MainApp.sResources.getString(R.string.objectives_4_gate), + MainApp.gs(R.string.objectives_4_objective), + MainApp.gs(R.string.objectives_4_gate), new Date(0), 1, new Date(0))); objectives.add(new Objective(5, - MainApp.sResources.getString(R.string.objectives_5_objective), - MainApp.sResources.getString(R.string.objectives_5_gate), + MainApp.gs(R.string.objectives_5_objective), + MainApp.gs(R.string.objectives_5_gate), new Date(0), 7, new Date(0))); objectives.add(new Objective(6, - MainApp.sResources.getString(R.string.objectives_6_objective), + MainApp.gs(R.string.objectives_6_objective), "", new Date(0), 28, new Date(0))); objectives.add(new Objective(7, - MainApp.sResources.getString(R.string.objectives_7_objective), + MainApp.gs(R.string.objectives_7_objective), "", new Date(0), 28, @@ -294,60 +261,45 @@ public class ObjectivesPlugin implements PluginBase, ConstraintsInterface { * Constraints interface **/ @Override - public boolean isLoopEnabled() { - return objectives.get(0).started.getTime() > 0; + public Constraint isLoopInvokationAllowed(Constraint value) { + if (!objectives.get(0).isStarted()) + value.set(false, String.format(MainApp.gs(R.string.objectivenotstarted), 1), this); + return value; } @Override - public boolean isClosedModeEnabled() { - return objectives.get(3).started.getTime() > 0; + public Constraint isClosedLoopAllowed(Constraint value) { + if (!objectives.get(3).isStarted()) + value.set(false, String.format(MainApp.gs(R.string.objectivenotstarted), 4), this); + return value; } @Override - public boolean isAutosensModeEnabled() { - return objectives.get(5).started.getTime() > 0; + public Constraint isAutosensModeEnabled(Constraint value) { + if (!objectives.get(5).isStarted()) + value.set(false, String.format(MainApp.gs(R.string.objectivenotstarted), 6), this); + return value; } @Override - public boolean isAMAModeEnabled() { - return objectives.get(6).started.getTime() > 0; + public Constraint isAMAModeEnabled(Constraint value) { + if (!objectives.get(6).isStarted()) + value.set(false, String.format(MainApp.gs(R.string.objectivenotstarted), 7), this); + return value; } @Override - public boolean isSMBModeEnabled() { - return objectives.get(7).started.getTime() > 0; + public Constraint isSMBModeEnabled(Constraint value) { + if (!objectives.get(7).isStarted()) + value.set(false, String.format(MainApp.gs(R.string.objectivenotstarted), 8), this); + return value; } @Override - public Double applyMaxIOBConstraints(Double maxIob) { - if (objectives.get(3).started.getTime() > 0 && objectives.get(3).accomplished.getTime() == 0) { - if (Config.logConstraintsChanges) - log.debug("Limiting maxIOB " + maxIob + " to " + 0 + "U"); - return 0d; - } else { - return maxIob; - } + public Constraint applyMaxIOBConstraints(Constraint maxIob) { + if (objectives.get(3).isStarted() && !objectives.get(3).isFinished()) + maxIob.set(0d, String.format(MainApp.gs(R.string.objectivenotfinished), 4), this); + return maxIob; } - @Override - public Double applyBasalConstraints(Double absoluteRate) { - return absoluteRate; - } - - @Override - public Integer applyBasalConstraints(Integer percentRate) { - return percentRate; - } - - @Override - public Double applyBolusConstraints(Double insulin) { - return insulin; - } - - @Override - public Integer applyCarbsConstraints(Integer carbs) { - return carbs; - } - - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ConstraintsSafety/SafetyPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/ConstraintsSafety/SafetyPlugin.java index 42f50d52dd..6a9613e92a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ConstraintsSafety/SafetyPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ConstraintsSafety/SafetyPlugin.java @@ -1,19 +1,22 @@ package info.nightscout.androidaps.plugins.ConstraintsSafety; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Objects; - -import info.nightscout.androidaps.BuildConfig; -import info.nightscout.androidaps.Config; -import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.ConstraintChecker; import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.interfaces.BgSourceInterface; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.ConstraintsInterface; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.OpenAPSAMA.OpenAPSAMAPlugin; +import info.nightscout.androidaps.plugins.OpenAPSMA.OpenAPSMAPlugin; +import info.nightscout.androidaps.plugins.OpenAPSSMB.OpenAPSSMBPlugin; +import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; +import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.HardLimits; import info.nightscout.utils.Round; import info.nightscout.utils.SP; @@ -21,8 +24,7 @@ import info.nightscout.utils.SP; /** * Created by mike on 05.08.2016. */ -public class SafetyPlugin implements PluginBase, ConstraintsInterface { - private static Logger log = LoggerFactory.getLogger(SafetyPlugin.class); +public class SafetyPlugin extends PluginBase implements ConstraintsInterface { static SafetyPlugin plugin = null; @@ -32,201 +34,154 @@ public class SafetyPlugin implements PluginBase, ConstraintsInterface { return plugin; } - @Override - public String getFragmentClass() { - return null; - } - - @Override - public int getType() { - return PluginBase.CONSTRAINTS; - } - - @Override - public String getName() { - return MainApp.instance().getString(R.string.safety); - } - - @Override - public String getNameShort() { - // use long name as fallback (no tabs) - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == CONSTRAINTS; - } - - @Override - public boolean isVisibleInTabs(int type) { - return false; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return false; - } - - @Override - public boolean showInList(int type) { - return false; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - } - - @Override - public int getPreferencesId() { - return R.xml.pref_safety; - } - - @Override - public boolean isLoopEnabled() { - return ConfigBuilderPlugin.getActivePump().getPumpDescription().isTempBasalCapable; + public SafetyPlugin() { + super(new PluginDescription() + .mainType(PluginType.CONSTRAINTS) + .neverVisible(true) + .alwaysEnabled(true) + .showInList(false) + .pluginName(R.string.safety) + .preferencesId(R.xml.pref_safety) + ); } /** * Constraints interface **/ @Override - public boolean isClosedModeEnabled() { + public Constraint isLoopInvokationAllowed(Constraint value) { + if (!ConfigBuilderPlugin.getActivePump().getPumpDescription().isTempBasalCapable) + value.set(false, MainApp.gs(R.string.pumpisnottempbasalcapable), this); + return value; + } + + @Override + public Constraint isClosedLoopAllowed(Constraint value) { String mode = SP.getString("aps_mode", "open"); - return mode.equals("closed") && BuildConfig.CLOSEDLOOP; + if (!mode.equals("closed")) + value.set(false, MainApp.gs(R.string.closedmodedisabledinpreferences), this); + + if (!MainApp.isEngineeringModeOrRelease()) { + if (value.value()) { + Notification n = new Notification(Notification.TOAST_ALARM, MainApp.gs(R.string.closed_loop_disabled_on_dev_branch), Notification.NORMAL); + MainApp.bus().post(new EventNewNotification(n)); + } + value.set(false, MainApp.gs(R.string.closed_loop_disabled_on_dev_branch), this); + } + + return value; } @Override - public boolean isAutosensModeEnabled() { - return true; + public Constraint isAutosensModeEnabled(Constraint value) { + boolean enabled = SP.getBoolean(R.string.key_openapsama_useautosens, false); + if (!enabled) + value.set(false, MainApp.gs(R.string.autosensdisabledinpreferences), this); + return value; } @Override - public boolean isAMAModeEnabled() { - return true; + public Constraint isSMBModeEnabled(Constraint value) { + boolean enabled = SP.getBoolean(R.string.key_use_smb, false); + if (!enabled) + value.set(false, MainApp.gs(R.string.smbdisabledinpreferences), this); + ConstraintChecker constraintChecker = MainApp.getConstraintChecker(); + Constraint closedLoop = constraintChecker.isClosedLoopAllowed(); + if (!closedLoop.value()) + value.set(false, MainApp.gs(R.string.smbnotallowedinopenloopmode), this); + return value; } @Override - public boolean isSMBModeEnabled() { - return true; + public Constraint isAdvancedFilteringEnabled(Constraint value) { + BgSourceInterface bgSource = MainApp.getConfigBuilder().getActiveBgSource(); + + if (bgSource != null) { + if (!bgSource.advancedFilteringSupported()) + value.set(false, MainApp.gs(R.string.smbalwaysdisabled), this); + } + return value; } @Override - public Double applyBasalConstraints(Double absoluteRate) { - Double origAbsoluteRate = absoluteRate; - Double maxBasal = SP.getDouble("openapsma_max_basal", 1d); + public Constraint applyBasalConstraints(Constraint absoluteRate, Profile profile) { - Profile profile = MainApp.getConfigBuilder().getProfile(); - if (profile == null) return absoluteRate; - if (absoluteRate < 0) absoluteRate = 0d; + absoluteRate.setIfGreater(0d, String.format(MainApp.gs(R.string.limitingbasalratio), 0d, MainApp.gs(R.string.itmustbepositivevalue)), this); + + double maxBasal = SP.getDouble(R.string.key_openapsma_max_basal, 1d); + absoluteRate.setIfSmaller(maxBasal, String.format(MainApp.gs(R.string.limitingbasalratio), maxBasal, MainApp.gs(R.string.maxvalueinpreferences)), this); - Integer maxBasalMult = SP.getInt("openapsama_current_basal_safety_multiplier", 4); - Integer maxBasalFromDaily = SP.getInt("openapsama_max_daily_safety_multiplier", 3); // Check percentRate but absolute rate too, because we know real current basal in pump - Double origRate = absoluteRate; - if (absoluteRate > maxBasal) { - absoluteRate = maxBasal; - if (Config.logConstraintsChanges && origAbsoluteRate != Constants.basalAbsoluteOnlyForCheckLimit) - log.debug("Limiting rate " + origRate + " by maxBasal preference to " + absoluteRate + "U/h"); - } - if (absoluteRate > maxBasalMult * profile.getBasal()) { - absoluteRate = Math.floor(maxBasalMult * profile.getBasal() * 100) / 100; - if (Config.logConstraintsChanges && origAbsoluteRate != Constants.basalAbsoluteOnlyForCheckLimit) - log.debug("Limiting rate " + origRate + " by maxBasalMult to " + absoluteRate + "U/h"); - } - if (absoluteRate > profile.getMaxDailyBasal() * maxBasalFromDaily) { - absoluteRate = profile.getMaxDailyBasal() * maxBasalFromDaily; - if (Config.logConstraintsChanges && origAbsoluteRate != Constants.basalAbsoluteOnlyForCheckLimit) - log.debug("Limiting rate " + origRate + " by 3 * maxDailyBasal to " + absoluteRate + "U/h"); - } + Double maxBasalMult = SP.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4d); + double maxFromBasalMult = Math.floor(maxBasalMult * profile.getBasal() * 100) / 100; + absoluteRate.setIfSmaller(maxFromBasalMult, String.format(MainApp.gs(R.string.limitingbasalratio), maxFromBasalMult, MainApp.gs(R.string.maxbasalmultiplier)), this); + + Double maxBasalFromDaily = SP.getDouble(R.string.key_openapsama_max_daily_safety_multiplier, 3d); + double maxFromDaily = Math.floor(profile.getMaxDailyBasal() * maxBasalFromDaily * 100) / 100; + absoluteRate.setIfSmaller(maxFromDaily, String.format(MainApp.gs(R.string.limitingbasalratio), maxFromDaily, MainApp.gs(R.string.maxdailybasalmultiplier)), this); + + absoluteRate.setIfSmaller(HardLimits.maxBasal(), String.format(MainApp.gs(R.string.limitingbasalratio), HardLimits.maxBasal(), MainApp.gs(R.string.hardlimit)), this); return absoluteRate; } @Override - public Integer applyBasalConstraints(Integer percentRate) { - Integer origPercentRate = percentRate; - Double maxBasal = SP.getDouble("openapsma_max_basal", 1d); + public Constraint applyBasalPercentConstraints(Constraint percentRate, Profile profile) { - Profile profile = MainApp.getConfigBuilder().getProfile(); - if (profile == null) return percentRate; Double currentBasal = profile.getBasal(); + Double absoluteRate = currentBasal * ((double) percentRate.originalValue() / 100); - Double absoluteRate = currentBasal * ((double) percentRate / 100); + percentRate.addReason("Percent rate " + percentRate.originalValue() + "% recalculated to " + DecimalFormatter.to2Decimal(absoluteRate) + " U/h with current basal " + DecimalFormatter.to2Decimal(currentBasal) + " U/h", this); - if (Config.logConstraintsChanges) - log.debug("Percent rate " + percentRate + "% recalculated to " + absoluteRate + "U/h with current basal " + currentBasal + "U/h"); + Constraint absoluteConstraint = new Constraint<>(absoluteRate); + applyBasalConstraints(absoluteConstraint, profile); + percentRate.copyReasons(absoluteConstraint); - if (absoluteRate < 0) absoluteRate = 0d; - - Integer maxBasalMult = SP.getInt("openapsama_current_basal_safety_multiplier", 4); - Integer maxBasalFromDaily = SP.getInt("openapsama_max_daily_safety_multiplier", 3); - // Check percentRate but absolute rate too, because we know real current basal in pump - Double origRate = absoluteRate; - if (absoluteRate > maxBasal) { - absoluteRate = maxBasal; - if (Config.logConstraintsChanges && !Objects.equals(origPercentRate, Constants.basalPercentOnlyForCheckLimit)) - log.debug("Limiting rate " + origRate + " by maxBasal preference to " + absoluteRate + "U/h"); - } - if (absoluteRate > maxBasalMult * profile.getBasal()) { - absoluteRate = Math.floor(maxBasalMult * profile.getBasal() * 100) / 100; - if (Config.logConstraintsChanges && !Objects.equals(origPercentRate, Constants.basalPercentOnlyForCheckLimit)) - log.debug("Limiting rate " + origRate + " by maxBasalMult to " + absoluteRate + "U/h"); - } - if (absoluteRate > profile.getMaxDailyBasal() * maxBasalFromDaily) { - absoluteRate = profile.getMaxDailyBasal() * maxBasalFromDaily; - if (Config.logConstraintsChanges && !Objects.equals(origPercentRate, Constants.basalPercentOnlyForCheckLimit)) - log.debug("Limiting rate " + origRate + " by 3 * maxDailyBasal to " + absoluteRate + "U/h"); - } - - Integer percentRateAfterConst = new Double(absoluteRate / currentBasal * 100).intValue(); + Integer percentRateAfterConst = Double.valueOf(absoluteConstraint.value() / currentBasal * 100).intValue(); if (percentRateAfterConst < 100) percentRateAfterConst = Round.ceilTo((double) percentRateAfterConst, 10d).intValue(); else percentRateAfterConst = Round.floorTo((double) percentRateAfterConst, 10d).intValue(); - if (Config.logConstraintsChanges && !Objects.equals(origPercentRate, Constants.basalPercentOnlyForCheckLimit)) - log.debug("Recalculated percent rate " + percentRate + "% to " + percentRateAfterConst + "%"); - return percentRateAfterConst; + percentRate.set(percentRateAfterConst, String.format(MainApp.gs(R.string.limitingpercentrate), percentRateAfterConst, MainApp.gs(R.string.pumplimit)), this); + + return percentRate; } @Override - public Double applyBolusConstraints(Double insulin) { - try { - Double maxBolus = SP.getDouble("treatmentssafety_maxbolus", 3d); + public Constraint applyBolusConstraints(Constraint insulin) { + insulin.setIfGreater(0d, String.format(MainApp.gs(R.string.limitingbolus), 0d, MainApp.gs(R.string.itmustbepositivevalue)), this); - if (insulin < 0) insulin = 0d; - if (insulin > maxBolus) insulin = maxBolus; - } catch (Exception e) { - insulin = 0d; - } - if (insulin > HardLimits.maxBolus()) insulin = HardLimits.maxBolus(); + Double maxBolus = SP.getDouble(R.string.key_treatmentssafety_maxbolus, 3d); + insulin.setIfSmaller(maxBolus, String.format(MainApp.gs(R.string.limitingbolus), maxBolus, MainApp.gs(R.string.maxvalueinpreferences)), this); + + insulin.setIfSmaller(HardLimits.maxBolus(), String.format(MainApp.gs(R.string.limitingbolus), HardLimits.maxBolus(), MainApp.gs(R.string.hardlimit)), this); return insulin; } @Override - public Integer applyCarbsConstraints(Integer carbs) { - try { - Integer maxCarbs = SP.getInt("treatmentssafety_maxcarbs", 48); + public Constraint applyCarbsConstraints(Constraint carbs) { + carbs.setIfGreater(0, String.format(MainApp.gs(R.string.limitingcarbs), 0, MainApp.gs(R.string.itmustbepositivevalue)), this); + + Integer maxCarbs = SP.getInt(R.string.key_treatmentssafety_maxcarbs, 48); + carbs.setIfSmaller(maxCarbs, String.format(MainApp.gs(R.string.limitingcarbs), maxCarbs, MainApp.gs(R.string.maxvalueinpreferences)), this); - if (carbs < 0) carbs = 0; - if (carbs > maxCarbs) carbs = maxCarbs; - } catch (Exception e) { - carbs = 0; - } return carbs; } @Override - public Double applyMaxIOBConstraints(Double maxIob) { + public Constraint applyMaxIOBConstraints(Constraint maxIob) { + double maxIobPref; + if (OpenAPSSMBPlugin.getPlugin().isEnabled(PluginType.APS)) + maxIobPref = SP.getDouble(R.string.key_openapssmb_max_iob, 3d); + else + maxIobPref = SP.getDouble(R.string.key_openapsma_max_iob, 1.5d); + maxIob.setIfSmaller(maxIobPref, String.format(MainApp.gs(R.string.limitingiob), maxIobPref, MainApp.gs(R.string.maxvalueinpreferences)), this); + + if (OpenAPSMAPlugin.getPlugin().isEnabled(PluginType.APS)) + maxIob.setIfSmaller(HardLimits.maxIobAMA(), String.format(MainApp.gs(R.string.limitingiob), HardLimits.maxIobAMA(), MainApp.gs(R.string.hardlimit)), this); + if (OpenAPSAMAPlugin.getPlugin().isEnabled(PluginType.APS)) + maxIob.setIfSmaller(HardLimits.maxIobAMA(), String.format(MainApp.gs(R.string.limitingiob), HardLimits.maxIobAMA(), MainApp.gs(R.string.hardlimit)), this); + if (OpenAPSSMBPlugin.getPlugin().isEnabled(PluginType.APS)) + maxIob.setIfSmaller(HardLimits.maxIobSMB(), String.format(MainApp.gs(R.string.limitingiob), HardLimits.maxIobSMB(), MainApp.gs(R.string.hardlimit)), this); return maxIob; } diff --git a/app/src/main/java/info/nightscout/androidaps/db/Food.java b/app/src/main/java/info/nightscout/androidaps/plugins/Food/Food.java similarity index 55% rename from app/src/main/java/info/nightscout/androidaps/db/Food.java rename to app/src/main/java/info/nightscout/androidaps/plugins/Food/Food.java index cb856df073..49da1d6ac5 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/Food.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Food/Food.java @@ -1,21 +1,22 @@ -package info.nightscout.androidaps.db; +package info.nightscout.androidaps.plugins.Food; import com.j256.ormlite.field.DatabaseField; import com.j256.ormlite.table.DatabaseTable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.json.JSONException; +import org.json.JSONObject; import java.util.Objects; +import info.nightscout.utils.JsonHelper; + /** * Created by mike on 20.09.2017. */ - -@DatabaseTable(tableName = DatabaseHelper.DATABASE_FOODS) +@DatabaseTable(tableName = Food.TABLE_FOODS) public class Food { - private static Logger log = LoggerFactory.getLogger(Food.class); + static final String TABLE_FOODS = "Foods"; @DatabaseField(id = true) public long key; @@ -60,10 +61,29 @@ public class Food { @DatabaseField public int gi; // not used yet - public Food() { + private Food() { key = System.currentTimeMillis(); } + public static Food createFromJson(JSONObject json) throws JSONException { + Food food = new Food(); + if ("food".equals(JsonHelper.safeGetString(json, "type"))) { + food._id = JsonHelper.safeGetString(json, "_id"); + food.category = JsonHelper.safeGetString(json, "category"); + food.subcategory = JsonHelper.safeGetString(json, "subcategory"); + food.name = JsonHelper.safeGetString(json, "name"); + food.units = JsonHelper.safeGetString(json, "unit"); + food.portion = JsonHelper.safeGetDouble(json, "portion"); + food.carbs = JsonHelper.safeGetInt(json, "carbs"); + food.gi = JsonHelper.safeGetInt(json, "gi"); + food.energy = JsonHelper.safeGetInt(json, "energy"); + food.protein = JsonHelper.safeGetInt(json, "protein"); + food.fat = JsonHelper.safeGetInt(json, "fat"); + } + + return food; + } + public boolean isEqual(Food other) { if (portion != other.portion) return false; @@ -104,4 +124,22 @@ public class Food { units = other.units; gi = other.gi; } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("_id=" + _id + ";"); + sb.append("isValid=" + isValid + ";"); + sb.append("name=" + name + ";"); + sb.append("category=" + category + ";"); + sb.append("subcategory=" + subcategory + ";"); + sb.append("portion=" + portion + ";"); + sb.append("carbs=" + carbs + ";"); + sb.append("protein=" + protein + ";"); + sb.append("energy=" + energy + ";"); + sb.append("units=" + units + ";"); + sb.append("gi=" + gi + ";"); + + return sb.toString(); + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodFragment.java index a5c00aab7f..490146cc14 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodFragment.java @@ -18,7 +18,6 @@ import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; import com.squareup.otto.Subscribe; import org.slf4j.Logger; @@ -27,12 +26,13 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Set; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.db.Food; import info.nightscout.androidaps.events.EventFoodDatabaseChanged; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.NSUpload; import info.nightscout.utils.SpinnerHelper; @@ -121,7 +121,8 @@ public class FoodFragment extends SubscriberFragment { } }); - RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainApp.getDbHelper().foodHelper.getFoodData()); + RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainApp + .getSpecificPlugin(FoodPlugin.class).getService().getFoodData()); recyclerView.setAdapter(adapter); loadData(); @@ -130,7 +131,7 @@ public class FoodFragment extends SubscriberFragment { filterData(); return view; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } return null; @@ -144,20 +145,19 @@ public class FoodFragment extends SubscriberFragment { } void loadData() { - unfiltered = MainApp.getDbHelper().foodHelper.getFoodData(); + unfiltered = MainApp.getSpecificPlugin(FoodPlugin.class).getService().getFoodData(); } void fillCategories() { - categories = new ArrayList<>(); + Set catSet = new HashSet<>(); for (Food f : unfiltered) { if (f.category != null && !f.category.equals("")) - categories.add(f.category); + catSet.add(f.category); } // make it unique - categories = new ArrayList<>(new HashSet<>(categories)); - + categories = new ArrayList<>(catSet); categories.add(0, MainApp.sResources.getString(R.string.none)); ArrayAdapter adapterCategories = new ArrayAdapter<>(getContext(), @@ -167,19 +167,19 @@ public class FoodFragment extends SubscriberFragment { void fillSubcategories() { String categoryFilter = category.getSelectedItem().toString(); - subcategories = new ArrayList<>(); + + Set subCatSet = new HashSet<>(); if (!categoryFilter.equals(EMPTY)) { for (Food f : unfiltered) { if (f.category != null && f.category.equals(categoryFilter)) if (f.subcategory != null && !f.subcategory.equals("")) - subcategories.add(f.subcategory); + subCatSet.add(f.subcategory); } } // make it unique - subcategories = new ArrayList<>(new HashSet<>(subcategories)); - + subcategories = new ArrayList<>(subCatSet); subcategories.add(0, MainApp.sResources.getString(R.string.none)); ArrayAdapter adapterSubcategories = new ArrayAdapter<>(getContext(), @@ -299,7 +299,7 @@ public class FoodFragment extends SubscriberFragment { if (_id != null && !_id.equals("")) { NSUpload.removeFoodFromNS(_id); } - MainApp.getDbHelper().foodHelper.delete(food); + MainApp.getSpecificPlugin(FoodPlugin.class).getService().delete(food); } }); builder.setNegativeButton(MainApp.sResources.getString(R.string.cancel), null); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodPlugin.java index 255b4fd951..14eb318af4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodPlugin.java @@ -1,15 +1,14 @@ package info.nightscout.androidaps.plugins.Food; -import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; /** * Created by mike on 05.08.2016. */ -public class FoodPlugin implements PluginBase { - private boolean fragmentEnabled = true; - private boolean fragmentVisible = false; +public class FoodPlugin extends PluginBase { private static FoodPlugin plugin = null; @@ -19,67 +18,20 @@ public class FoodPlugin implements PluginBase { return plugin; } - @Override - public String getFragmentClass() { - return FoodFragment.class.getName(); + private FoodService service; + + private FoodPlugin() { + super(new PluginDescription() + .mainType(PluginType.GENERAL) + .fragmentClass(FoodFragment.class.getName()) + .pluginName(R.string.food) + .shortName(R.string.food_short) + ); + this.service = new FoodService(); } - @Override - public int getType() { - return PluginBase.GENERAL; + public FoodService getService() { + return this.service; } - @Override - public String getName() { - return MainApp.instance().getString(R.string.food); - } - - @Override - public String getNameShort() { - // use long name as fallback (not visible in tabs) - return getName(); - } - - - @Override - public boolean isEnabled(int type) { - return type == GENERAL && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == GENERAL && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == GENERAL) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == GENERAL) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return -1; - } - - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodService.java b/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodService.java new file mode 100644 index 0000000000..44b96850ae --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodService.java @@ -0,0 +1,369 @@ +package info.nightscout.androidaps.plugins.Food; + +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +import android.support.annotation.Nullable; + +import com.j256.ormlite.android.apptools.OpenHelperManager; +import com.j256.ormlite.android.apptools.OrmLiteBaseService; +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.DaoManager; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.table.TableUtils; +import com.squareup.otto.Subscribe; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.db.DatabaseHelper; +import info.nightscout.androidaps.db.ICallback; +import info.nightscout.androidaps.events.Event; +import info.nightscout.androidaps.events.EventFoodDatabaseChanged; +import info.nightscout.androidaps.events.EventNsFood; + +/** + * Created by mike on 24.09.2017. + */ + +public class FoodService extends OrmLiteBaseService { + private static Logger log = LoggerFactory.getLogger(FoodService.class); + + private static final ScheduledExecutorService foodEventWorker = Executors.newSingleThreadScheduledExecutor(); + private static ScheduledFuture scheduledFoodEventPost = null; + + public FoodService() { + onCreate(); + dbInitialize(); + MainApp.bus().register(this); + } + + /** + * This method is a simple re-implementation of the database create and up/downgrade functionality + * in SQLiteOpenHelper#getDatabaseLocked method. + *

+ * It is implemented to be able to late initialize separate plugins of the application. + */ + protected void dbInitialize() { + DatabaseHelper helper = OpenHelperManager.getHelper(this, DatabaseHelper.class); + int newVersion = helper.getNewVersion(); + int oldVersion = helper.getOldVersion(); + + if (oldVersion > newVersion) { + onDowngrade(this.getConnectionSource(), oldVersion, newVersion); + } else { + onUpgrade(this.getConnectionSource(), oldVersion, newVersion); + } + } + + public Dao getDao() { + try { + return DaoManager.createDao(this.getConnectionSource(), Food.class); + } catch (SQLException e) { + log.error("Cannot create Dao for Food.class"); + } + + return null; + } + + @Subscribe + public void handleNsEvent(EventNsFood event) { + int mode = event.getMode(); + Bundle payload = event.getPayload(); + + try { + if (payload.containsKey("food")) { + JSONObject json = new JSONObject(payload.getString("food")); + if (mode == EventNsFood.ADD || mode == EventNsFood.UPDATE) { + this.createFoodFromJsonIfNotExists(json); + } else { + this.deleteNS(json); + } + } + + if (payload.containsKey("foods")) { + JSONArray array = new JSONArray(payload.getString("foods")); + if (mode == EventNsFood.ADD || mode == EventNsFood.UPDATE) { + this.createFoodFromJsonIfNotExists(array); + } else { + this.deleteNS(array); + } + } + } catch (JSONException e) { + log.error("Unhandled Exception", e); + } + } + + @Override + public void onCreate() { + super.onCreate(); + try { + log.info("onCreate"); + TableUtils.createTableIfNotExists(this.getConnectionSource(), Food.class); + } catch (SQLException e) { + log.error("Can't create database", e); + throw new RuntimeException(e); + } + } + + public void onUpgrade(ConnectionSource connectionSource, int oldVersion, int newVersion) { + if (oldVersion == 7 && newVersion == 8) { + log.debug("Upgrading database from v7 to v8"); + } else { + log.info("onUpgrade"); +// this.resetFood(); + } + } + + public void onDowngrade(ConnectionSource connectionSource, int oldVersion, int newVersion) { + // this method is not supported right now + } + + public void resetFood() { + try { + TableUtils.dropTable(this.getConnectionSource(), Food.class, true); + TableUtils.createTableIfNotExists(this.getConnectionSource(), Food.class); + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + scheduleFoodChange(); + } + + + /** + * A place to centrally register events to be posted, if any data changed. + * This should be implemented in an abstract service-class. + *

+ * We do need to make sure, that ICallback is extended to be able to handle multiple + * events, or handle a list of events. + *

+ * on some methods the earliestDataChange event is handled separatly, in that it is checked if it is + * set to null by another event already (eg. scheduleExtendedBolusChange). + * + * @param event + * @param eventWorker + * @param callback + */ + private void scheduleEvent(final Event event, ScheduledExecutorService eventWorker, + final ICallback callback) { + + class PostRunnable implements Runnable { + public void run() { + log.debug("Firing EventFoodChange"); + MainApp.bus().post(event); + callback.setPost(null); + } + } + // prepare task for execution in 1 sec + // cancel waiting task to prevent sending multiple posts + if (callback.getPost() != null) + callback.getPost().cancel(false); + Runnable task = new PostRunnable(); + final int sec = 1; + callback.setPost(eventWorker.schedule(task, sec, TimeUnit.SECONDS)); + } + + /** + * Schedule a foodChange Event. + */ + public void scheduleFoodChange() { + this.scheduleEvent(new EventFoodDatabaseChanged(), foodEventWorker, new ICallback() { + @Override + public void setPost(ScheduledFuture post) { + scheduledFoodEventPost = post; + } + + @Override + public ScheduledFuture getPost() { + return scheduledFoodEventPost; + } + }); + } + + public List getFoodData() { + try { + return this.getDao().queryForAll(); + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + + return new ArrayList<>(); + } + + /* + { + "_id": "551ee3ad368e06e80856e6a9", + "type": "food", + "category": "Zakladni", + "subcategory": "Napoje", + "name": "Mleko", + "portion": 250, + "carbs": 12, + "gi": 1, + "created_at": "2015-04-14T06:59:16.500Z", + "unit": "ml" + } + */ + public void createFoodFromJsonIfNotExists(JSONObject json) { + try { + Food food = Food.createFromJson(json); + this.createFoodFromJsonIfNotExists(food); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + } + + public void createFoodFromJsonIfNotExists(JSONArray array) { + try { + for (int n = 0; n < array.length(); n++) { + JSONObject json = array.getJSONObject(n); + Food food = Food.createFromJson(json); + this.createFoodFromJsonIfNotExists(food); + } + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + } + + public void createFoodFromJsonIfNotExists(Food food) { + this.createOrUpdateByNS(food); + } + + public void deleteNS(JSONObject json) { + try { + String _id = json.getString("_id"); + this.deleteByNSId(_id); + } catch (JSONException | SQLException e) { + log.error("Unhandled exception", e); + } + } + + public void deleteNS(JSONArray array) { + try { + for (int n = 0; n < array.length(); n++) { + JSONObject json = array.getJSONObject(n); + this.deleteNS(json); + } + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + } + + /** + * deletes an entry by its NS Id. + *

+ * Basically a convenience method for findByNSId and delete. + * + * @param _id + */ + public void deleteByNSId(String _id) throws SQLException { + Food stored = this.findByNSId(_id); + if (stored != null) { + log.debug("FOOD: Removing Food record from database: " + stored.toString()); + this.delete(stored); + } + } + + /** + * deletes the food and sends the foodChange Event + *

+ * should be moved ot a Service + * + * @param food + */ + public void delete(Food food) { + try { + this.getDao().delete(food); + this.scheduleFoodChange(); + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + } + + /** + * Create of update a food record by the NS (Nightscout) Id. + * + * @param food + * @return + */ + public boolean createOrUpdateByNS(Food food) { + // find by NS _id + if (food._id != null && !food._id.equals("")) { + Food old = this.findByNSId(food._id); + + if (old != null) { + if (!old.isEqual(food)) { + this.delete(old); // need to delete/create because date may change too + old.copyFrom(food); + this.create(old); + return true; + } else { + return false; + } + } else { + this.createOrUpdate(food); + return true; + } + } + + return false; + } + + public void createOrUpdate(Food food) { + try { + this.getDao().createOrUpdate(food); + log.debug("FOOD: Created or Updated: " + food.toString()); + } catch (SQLException e) { + log.error("Unable to createOrUpdate Food", e); + } + this.scheduleFoodChange(); + } + + public void create(Food food) { + try { + this.getDao().create(food); + log.debug("FOOD: New record: " + food.toString()); + } catch (SQLException e) { + log.error("Unable to create Food", e); + } + this.scheduleFoodChange(); + } + + /** + * finds food by its NS Id. + * + * @param _id + * @return + */ + @Nullable + public Food findByNSId(String _id) { + try { + List list = this.getDao().queryForEq("_id", _id); + + if (list.size() == 1) { // really? if there are more then one result, then we do not return anything... + return list.get(0); + } + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return null; + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/ActivityGraph.java b/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/ActivityGraph.java index 5a0d7fcce9..8bbd1d3e7f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/ActivityGraph.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/ActivityGraph.java @@ -12,7 +12,7 @@ import java.util.ArrayList; import java.util.List; import info.nightscout.androidaps.data.Iob; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.androidaps.interfaces.InsulinInterface; /** diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinFastactingPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinFastactingPlugin.java deleted file mode 100644 index 6218a2ecfe..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinFastactingPlugin.java +++ /dev/null @@ -1,136 +0,0 @@ -package info.nightscout.androidaps.plugins.Insulin; - -import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.Iob; -import info.nightscout.androidaps.db.Treatment; -import info.nightscout.androidaps.interfaces.InsulinInterface; -import info.nightscout.androidaps.interfaces.PluginBase; - -/** - * Created by mike on 17.04.2017. - */ - -public class InsulinFastactingPlugin implements PluginBase, InsulinInterface { - - private boolean fragmentEnabled = true; - private boolean fragmentVisible = false; - - private static InsulinFastactingPlugin plugin = null; - - public static InsulinFastactingPlugin getPlugin() { - if (plugin == null) - plugin = new InsulinFastactingPlugin(); - return plugin; - } - - @Override - public int getType() { - return INSULIN; - } - - @Override - public String getFragmentClass() { - return InsulinFragment.class.getName(); - } - - @Override - public String getName() { - return MainApp.sResources.getString(R.string.fastactinginsulin); - } - - @Override - public String getNameShort() { - return MainApp.sResources.getString(R.string.insulin_shortname); - } - - @Override - public boolean isEnabled(int type) { - return type == INSULIN && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == INSULIN && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == INSULIN) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == INSULIN) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return -1; - } - - // Insulin interface - @Override - public int getId() { - return FASTACTINGINSULIN; - } - - @Override - public String getFriendlyName() { - return MainApp.sResources.getString(R.string.fastactinginsulin); - } - - @Override - public String getComment() { - return MainApp.sResources.getString(R.string.fastactinginsulincomment); - } - - @Override - public double getDia() { - return MainApp.getConfigBuilder().getProfile() != null ? MainApp.getConfigBuilder().getProfile().getDia() : Constants.defaultDIA; - } - - @Override - public Iob iobCalcForTreatment(Treatment treatment, long time, double dia) { - Iob result = new Iob(); - - double scaleFactor = 3.0 / dia; - double peak = 75d; - double end = 180d; - - if (treatment.insulin != 0d) { - long bolusTime = treatment.date; - double minAgo = scaleFactor * (time - bolusTime) / 1000d / 60d; - - if (minAgo < peak) { - double x1 = minAgo / 5d + 1; - result.iobContrib = treatment.insulin * (1 - 0.001852 * x1 * x1 + 0.001852 * x1); - // units: BG (mg/dL) = (BG/U) * U insulin * scalar - result.activityContrib = treatment.insulin * (2 / dia / 60 / peak) * minAgo; - - } else if (minAgo < end) { - double x2 = (minAgo - 75) / 5; - result.iobContrib = treatment.insulin * (0.001323 * x2 * x2 - 0.054233 * x2 + 0.55556); - result.activityContrib = treatment.insulin * (2 / dia / 60 - (minAgo - peak) * 2 / dia / 60 / (60 * 3 - peak)); - } - } - return result; - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinFastactingProlongedPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinFastactingProlongedPlugin.java deleted file mode 100644 index 4155a9be56..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinFastactingProlongedPlugin.java +++ /dev/null @@ -1,141 +0,0 @@ -package info.nightscout.androidaps.plugins.Insulin; - -import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.Iob; -import info.nightscout.androidaps.db.Treatment; -import info.nightscout.androidaps.interfaces.InsulinInterface; -import info.nightscout.androidaps.interfaces.PluginBase; - -/** - * Created by mike on 17.04.2017. - */ - -public class InsulinFastactingProlongedPlugin implements PluginBase, InsulinInterface { - - private boolean fragmentEnabled = false; - private boolean fragmentVisible = false; - - private static InsulinFastactingProlongedPlugin plugin = null; - - public static InsulinFastactingProlongedPlugin getPlugin() { - if (plugin == null) - plugin = new InsulinFastactingProlongedPlugin(); - return plugin; - } - - @Override - public int getType() { - return INSULIN; - } - - @Override - public String getFragmentClass() { - return InsulinFragment.class.getName(); - } - - @Override - public String getName() { - return MainApp.sResources.getString(R.string.fastactinginsulinprolonged); - } - - @Override - public String getNameShort() { - return MainApp.sResources.getString(R.string.insulin_shortname); - } - - @Override - public boolean isEnabled(int type) { - return type == INSULIN && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == INSULIN && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == INSULIN) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == INSULIN) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return -1; - } - - // Insulin interface - @Override - public int getId() { - return FASTACTINGINSULINPROLONGED; - } - - @Override - public String getFriendlyName() { - return MainApp.sResources.getString(R.string.fastactinginsulinprolonged); - } - - @Override - public String getComment() { - return MainApp.sResources.getString(R.string.fastactinginsulincomment); - } - - @Override - public double getDia() { - return MainApp.getConfigBuilder().getProfile() != null ? MainApp.getConfigBuilder().getProfile().getDia() : Constants.defaultDIA; - } - - @Override - public Iob iobCalcForTreatment(Treatment treatment, long time, double dia) { - Iob result = new Iob(); - - //Double scaleFactor = 3.0 / dia; - double peak = 75d * dia / 6.0; - double tail = 180d * dia / 6.0; - double end = 360d * dia / 6.0; - double Total = 2 * peak + (tail - peak) * 5 / 2 + (end - tail) / 2; - - if (treatment.insulin != 0d) { - long bolusTime = treatment.date; - double minAgo = (time - bolusTime) / 1000d / 60d; - - if (minAgo < peak) { - double x1 = 6 / dia * minAgo / 5d + 1; - result.iobContrib = treatment.insulin * (1 - 0.0012595 * x1 * x1 + 0.0012595 * x1); - // units: BG (mg/dL) = (BG/U) * U insulin * scalar - result.activityContrib = treatment.insulin * ((2 * peak / Total) * 2 / peak / peak * minAgo); - } else if (minAgo < tail) { - double x2 = (6 / dia * (minAgo - peak)) / 5; - result.iobContrib = treatment.insulin * (0.00074 * x2 * x2 - 0.0403 * x2 + 0.69772); - result.activityContrib = treatment.insulin * (-((2 * peak / Total) * 2 / peak * 3 / 4) / (tail - peak) * (minAgo - peak) + (2 * peak / Total) * 2 / peak); - } else if (minAgo < end) { - double x3 = (6 / dia * (minAgo - tail)) / 5; - result.iobContrib = treatment.insulin * (0.0001323 * x3 * x3 - 0.0097 * x3 + 0.17776); - result.activityContrib = treatment.insulin * (-((2 * peak / Total) * 2 / peak * 1 / 4) / (end - tail) * (minAgo - tail) + (2 * peak / Total) * 2 / peak / 4); - } - - } - return result; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinFragment.java index f9a9ce38fc..a171d646e9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinFragment.java @@ -7,11 +7,11 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.utils.FabricPrivacy; /** * Created by mike on 17.04.2017. @@ -37,7 +37,7 @@ public class InsulinFragment extends Fragment { return view; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } return null; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefBasePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefBasePlugin.java index b91eefdc9c..672c5cfeb6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefBasePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefBasePlugin.java @@ -5,45 +5,31 @@ import com.squareup.otto.Bus; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Iob; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.androidaps.interfaces.InsulinInterface; import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; /** * Created by adrian on 13.08.2017. */ -public abstract class InsulinOrefBasePlugin implements PluginBase, InsulinInterface { +public abstract class InsulinOrefBasePlugin extends PluginBase implements InsulinInterface { public static double MIN_DIA = 5; long lastWarned = 0; - @Override - public int getType() { - return INSULIN; - } - - @Override - public String getNameShort() { - return MainApp.sResources.getString(R.string.insulin_shortname); - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; + public InsulinOrefBasePlugin() { + super(new PluginDescription() + .mainType(PluginType.INSULIN) + .fragmentClass(InsulinFragment.class.getName()) + .pluginName(R.string.fastactinginsulin) + .shortName(R.string.insulin_shortname) + ); } public Bus getBus() { @@ -53,7 +39,7 @@ public abstract class InsulinOrefBasePlugin implements PluginBase, InsulinInterf @Override public double getDia() { double dia = getUserDefinedDia(); - if(dia >= MIN_DIA){ + if (dia >= MIN_DIA) { return dia; } else { sendShortDiaNotification(dia); @@ -62,7 +48,7 @@ public abstract class InsulinOrefBasePlugin implements PluginBase, InsulinInterf } void sendShortDiaNotification(double dia) { - if((System.currentTimeMillis() - lastWarned) > 60*1000) { + if ((System.currentTimeMillis() - lastWarned) > 60 * 1000) { lastWarned = System.currentTimeMillis(); Notification notification = new Notification(Notification.SHORT_DIA, String.format(this.getNotificationPattern(), dia, MIN_DIA), Notification.URGENT); this.getBus().post(new EventNewNotification(notification)); @@ -92,7 +78,7 @@ public abstract class InsulinOrefBasePlugin implements PluginBase, InsulinInterf long bolusTime = treatment.date; double t = (time - bolusTime) / 1000d / 60d; - double td = getDia()*60; //getDIA() always >= MIN_DIA + double td = getDia() * 60; //getDIA() always >= MIN_DIA double tp = peak; // force the IOB to 0 if over DIA hours have passed @@ -109,9 +95,9 @@ public abstract class InsulinOrefBasePlugin implements PluginBase, InsulinInterf @Override public String getComment() { - String comment = commentStandardText(); + String comment = commentStandardText(); double userDia = getUserDefinedDia(); - if(userDia < MIN_DIA){ + if (userDia < MIN_DIA) { comment += "\n" + String.format(MainApp.sResources.getString(R.string.dia_too_short), userDia, MIN_DIA); } return comment; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefFreePeakPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefFreePeakPlugin.java index 3cc2e6b9ca..3a19573d71 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefFreePeakPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefFreePeakPlugin.java @@ -10,9 +10,6 @@ import info.nightscout.utils.SP; public class InsulinOrefFreePeakPlugin extends InsulinOrefBasePlugin { - private boolean fragmentEnabled = false; - private boolean fragmentVisible = false; - private static InsulinOrefFreePeakPlugin plugin = null; public static InsulinOrefFreePeakPlugin getPlugin() { @@ -21,24 +18,20 @@ public class InsulinOrefFreePeakPlugin extends InsulinOrefBasePlugin { return plugin; } - public static final int DEFAULT_PEAK = 75; + private static final int DEFAULT_PEAK = 75; + + private InsulinOrefFreePeakPlugin() { + super(); + pluginDescription + .pluginName(R.string.free_peak_oref) + .preferencesId(R.xml.pref_insulinoreffreepeak); + } @Override public int getId() { return OREF_FREE_PEAK; } - @Override - public String getName() { - return MainApp.sResources.getString(R.string.free_peak_oref); - } - - @Override - public String getFragmentClass() { - return InsulinFragment.class.getName(); - } - - @Override public String getFriendlyName() { return MainApp.sResources.getString(R.string.free_peak_oref); } @@ -48,31 +41,6 @@ public class InsulinOrefFreePeakPlugin extends InsulinOrefBasePlugin { return MainApp.sResources.getString(R.string.insulin_peak_time) + ": " + getPeak(); } - @Override - public boolean isEnabled(int type) { - return type == INSULIN && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == INSULIN && fragmentVisible; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == INSULIN) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == INSULIN) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return R.xml.pref_insulinoreffreepeak; - } - @Override int getPeak() { return SP.getInt(R.string.key_insulin_oref_peak, DEFAULT_PEAK); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefRapidActingPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefRapidActingPlugin.java index cae9ac540d..cd7769b55b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefRapidActingPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefRapidActingPlugin.java @@ -9,9 +9,6 @@ import info.nightscout.androidaps.R; public class InsulinOrefRapidActingPlugin extends InsulinOrefBasePlugin { - private boolean fragmentEnabled = false; - private boolean fragmentVisible = false; - private static InsulinOrefRapidActingPlugin plugin = null; public static InsulinOrefRapidActingPlugin getPlugin() { @@ -20,23 +17,19 @@ public class InsulinOrefRapidActingPlugin extends InsulinOrefBasePlugin { return plugin; } - public static final int PEAK = 75; + private static final int PEAK = 75; + + private InsulinOrefRapidActingPlugin() { + super(); + pluginDescription + .pluginName(R.string.rapid_acting_oref); + } @Override public int getId() { return OREF_RAPID_ACTING; } - @Override - public String getName() { - return MainApp.sResources.getString(R.string.rapid_acting_oref); - } - - @Override - public String getFragmentClass() { - return InsulinFragment.class.getName(); - } - @Override public String getFriendlyName() { return MainApp.sResources.getString(R.string.rapid_acting_oref); @@ -47,31 +40,6 @@ public class InsulinOrefRapidActingPlugin extends InsulinOrefBasePlugin { return MainApp.sResources.getString(R.string.fastactinginsulincomment); } - @Override - public boolean isEnabled(int type) { - return type == INSULIN && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == INSULIN && fragmentVisible; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == INSULIN) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == INSULIN) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return -1; - } - @Override int getPeak() { return PEAK; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefUltraRapidActingPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefUltraRapidActingPlugin.java index 0d390a3ba2..0e20f8dd42 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefUltraRapidActingPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefUltraRapidActingPlugin.java @@ -9,9 +9,6 @@ import info.nightscout.androidaps.R; public class InsulinOrefUltraRapidActingPlugin extends InsulinOrefBasePlugin { - private boolean fragmentEnabled = false; - private boolean fragmentVisible = false; - private static InsulinOrefUltraRapidActingPlugin plugin = null; public static InsulinOrefUltraRapidActingPlugin getPlugin() { @@ -20,7 +17,13 @@ public class InsulinOrefUltraRapidActingPlugin extends InsulinOrefBasePlugin { return plugin; } - public static final int PEAK = 55; + private static final int PEAK = 55; + + private InsulinOrefUltraRapidActingPlugin() { + super(); + pluginDescription + .pluginName(R.string.ultrarapid_oref); + } @Override public int getId() { @@ -32,11 +35,6 @@ public class InsulinOrefUltraRapidActingPlugin extends InsulinOrefBasePlugin { return MainApp.sResources.getString(R.string.ultrarapid_oref); } - @Override - public String getFragmentClass() { - return InsulinFragment.class.getName(); - } - @Override public String getFriendlyName() { return MainApp.sResources.getString(R.string.ultrarapid_oref); @@ -47,31 +45,6 @@ public class InsulinOrefUltraRapidActingPlugin extends InsulinOrefBasePlugin { return MainApp.sResources.getString(R.string.ultrafastactinginsulincomment); } - @Override - public boolean isEnabled(int type) { - return type == INSULIN && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == INSULIN && fragmentVisible; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == INSULIN) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == INSULIN) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return -1; - } - @Override int getPeak() { return PEAK; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensData.java b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensData.java index e6d58178f5..693a6fad55 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensData.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensData.java @@ -7,22 +7,31 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; -import info.nightscout.androidaps.db.Treatment; -import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.plugins.OpenAPSSMB.SMBDefaults; +import info.nightscout.androidaps.plugins.Overview.graphExtensions.DataPointWithLabelInterface; +import info.nightscout.androidaps.plugins.Overview.graphExtensions.PointsWithLabelGraphSeries; +import info.nightscout.androidaps.plugins.Overview.graphExtensions.Scale; import info.nightscout.androidaps.plugins.SensitivityAAPS.SensitivityAAPSPlugin; import info.nightscout.androidaps.plugins.SensitivityWeightedAverage.SensitivityWeightedAveragePlugin; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.utils.SP; /** * Created by mike on 25.04.2017. */ -public class AutosensData { +public class AutosensData implements DataPointWithLabelInterface { private static Logger log = LoggerFactory.getLogger(AutosensData.class); + public void setChartTime(long chartTime) { + this.chartTime = chartTime; + } + static class CarbsInPast { long time = 0L; double carbs = 0d; @@ -33,20 +42,21 @@ public class AutosensData { time = t.date; carbs = t.carbs; remaining = t.carbs; - if (SensitivityAAPSPlugin.getPlugin().isEnabled(PluginBase.SENSITIVITY) || SensitivityWeightedAveragePlugin.getPlugin().isEnabled(PluginBase.SENSITIVITY)) { - double maxAbsorptionHours = SP.getDouble(R.string.key_absorption_maxtime, 4d); + if (SensitivityAAPSPlugin.getPlugin().isEnabled(PluginType.SENSITIVITY) || SensitivityWeightedAveragePlugin.getPlugin().isEnabled(PluginType.SENSITIVITY)) { + double maxAbsorptionHours = SP.getDouble(R.string.key_absorption_maxtime, Constants.DEFAULT_MAX_ABSORPTION_TIME); Profile profile = MainApp.getConfigBuilder().getProfile(t.date); double sens = Profile.toMgdl(profile.getIsf(t.date), profile.getUnits()); double ic = profile.getIc(t.date); min5minCarbImpact = t.carbs / (maxAbsorptionHours * 60 / 5) * sens / ic; log.debug("Min 5m carbs impact for " + carbs + "g @" + new Date(t.date).toLocaleString() + " for " + maxAbsorptionHours + "h calculated to " + min5minCarbImpact + " ISF: " + sens + " IC: " + ic); } else { - min5minCarbImpact = SP.getDouble("openapsama_min_5m_carbimpact", 3.0); + min5minCarbImpact = SP.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact); } } } public long time = 0L; + long chartTime; public String pastSensitivity = ""; public double deviation = 0d; boolean nonCarbsDeviation = false; @@ -57,26 +67,39 @@ public class AutosensData { public double cob = 0; public double bgi = 0d; public double delta = 0d; + public double avgDelta = 0d; + public double avgDeviation = 0d; public double autosensRatio = 1d; + public double slopeFromMaxDeviation = 0; + public double slopeFromMinDeviation = 999; + public double usedMinCarbsImpact = 0d; + public boolean failoverToMinAbsorbtionRate = false; - public String log(long time) { - return "AutosensData: " + new Date(time).toLocaleString() + " " + pastSensitivity + " Delta=" + delta + " Bgi=" + bgi + " Deviation=" + deviation + " Absorbed=" + absorbed + " CarbsFromBolus=" + carbsFromBolus + " COB=" + cob + " autosensRatio=" + autosensRatio; + @Override + public String toString() { + return "AutosensData: " + new Date(time).toLocaleString() + " " + pastSensitivity + " Delta=" + delta + " avgDelta=" + avgDelta + " Bgi=" + bgi + " Deviation=" + deviation + " avgDeviation=" + avgDeviation + " Absorbed=" + absorbed + " CarbsFromBolus=" + carbsFromBolus + " COB=" + cob + " autosensRatio=" + autosensRatio + " slopeFromMaxDeviation=" + slopeFromMaxDeviation + " slopeFromMinDeviation =" + slopeFromMinDeviation; } public int minOld() { return (int) ((System.currentTimeMillis() - time) / 1000 / 60); } - // remove carbs older than 4h + // remove carbs older than timeframe public void removeOldCarbs(long toTime) { + double maxAbsorptionHours = Constants.DEFAULT_MAX_ABSORPTION_TIME; + if (SensitivityAAPSPlugin.getPlugin().isEnabled(PluginType.SENSITIVITY) || SensitivityWeightedAveragePlugin.getPlugin().isEnabled(PluginType.SENSITIVITY)) { + maxAbsorptionHours = SP.getDouble(R.string.key_absorption_maxtime, Constants.DEFAULT_MAX_ABSORPTION_TIME); + } else { + maxAbsorptionHours = SP.getDouble(R.string.key_absorption_cutoff, Constants.DEFAULT_MAX_ABSORPTION_TIME); + } for (int i = 0; i < activeCarbsList.size(); i++) { CarbsInPast c = activeCarbsList.get(i); - if (c.time + 4 * 60 * 60 * 1000L < toTime) { + if (c.time + maxAbsorptionHours * 60 * 60 * 1000L < toTime) { activeCarbsList.remove(i--); if (c.remaining > 0) cob -= c.remaining; - log.debug("Removing carbs at "+ new Date(toTime).toLocaleString() + " + after 4h :" + new Date(c.time).toLocaleString()); + log.debug("Removing carbs at " + new Date(toTime).toLocaleString() + " + after " + maxAbsorptionHours + "h :" + new Date(c.time).toLocaleString()); } } } @@ -93,4 +116,57 @@ public class AutosensData { } } + // ------- DataPointWithLabelInterface ------ + + private Scale scale; + + public void setScale(Scale scale) { + this.scale = scale; + } + + @Override + public double getX() { + return chartTime; + } + + @Override + public double getY() { + return scale.transform(cob); + } + + @Override + public void setY(double y) { + + } + + @Override + public String getLabel() { + return null; + } + + @Override + public long getDuration() { + return 0; + } + + @Override + public PointsWithLabelGraphSeries.Shape getShape() { + return PointsWithLabelGraphSeries.Shape.COBFAILOVER; + } + + @Override + public float getSize() { + return 0.5f; + } + + @Override + public int getColor() { + return MainApp.gc(R.color.cob); + } + + @Override + public int getSecondColor() { + return 0; + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/CobInfo.java b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/CobInfo.java new file mode 100644 index 0000000000..90b72132bd --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/CobInfo.java @@ -0,0 +1,15 @@ +package info.nightscout.androidaps.plugins.IobCobCalculator; + +import android.support.annotation.Nullable; + +public class CobInfo { + /** All COB up to now, including carbs not yet processed by IobCob calculation. */ + @Nullable + public final Double displayCob; + public final double futureCarbs; + + public CobInfo(@Nullable Double displayCob, double futureCarbs) { + this.displayCob = displayCob; + this.futureCarbs = futureCarbs; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobCalculatorPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobCalculatorPlugin.java index 504c676c8d..2081e43929 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobCalculatorPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobCalculatorPlugin.java @@ -22,36 +22,30 @@ import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.events.Event; import info.nightscout.androidaps.events.EventAppInitialized; import info.nightscout.androidaps.events.EventConfigBuilderChange; import info.nightscout.androidaps.events.EventNewBG; import info.nightscout.androidaps.events.EventNewBasalProfile; import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventNewHistoryData; +import info.nightscout.androidaps.plugins.OpenAPSSMB.OpenAPSSMBPlugin; +import info.nightscout.androidaps.plugins.Treatments.Treatment; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.DateUtil; +import static info.nightscout.utils.DateUtil.now; + /** * Created by mike on 24.04.2017. */ -public class IobCobCalculatorPlugin implements PluginBase { - private static Logger log = LoggerFactory.getLogger(IobCobCalculatorPlugin.class); - - private static LongSparseArray iobTable = new LongSparseArray<>(); // oldest at index 0 - private static LongSparseArray autosensDataTable = new LongSparseArray<>(); // oldest at index 0 - private static LongSparseArray basalDataTable = new LongSparseArray<>(); // oldest at index 0 - - private static volatile List bgReadings = null; // newest at index 0 - private static volatile List bucketed_data = null; - - private static double dia = Constants.defaultDIA; - - static final Object dataLock = new Object(); - - boolean stopCalculationTrigger = false; - IobCobThread thread = null; +public class IobCobCalculatorPlugin extends PluginBase { + private Logger log = LoggerFactory.getLogger(IobCobCalculatorPlugin.class); private static IobCobCalculatorPlugin plugin = null; @@ -61,80 +55,52 @@ public class IobCobCalculatorPlugin implements PluginBase { return plugin; } - public static LongSparseArray getAutosensDataTable() { + private LongSparseArray iobTable = new LongSparseArray<>(); // oldest at index 0 + private LongSparseArray autosensDataTable = new LongSparseArray<>(); // oldest at index 0 + private LongSparseArray basalDataTable = new LongSparseArray<>(); // oldest at index 0 + + private volatile List bgReadings = null; // newest at index 0 + private volatile List bucketed_data = null; + + private double dia = Constants.defaultDIA; + + final Object dataLock = new Object(); + + boolean stopCalculationTrigger = false; + private IobCobThread thread = null; + + public IobCobCalculatorPlugin() { + super(new PluginDescription() + .mainType(PluginType.GENERAL) + .pluginName(R.string.iobcobcalculator) + .showInList(false) + .neverVisible(true) + .alwaysEnabled(true) + ); + } + + @Override + protected void onStart() { + MainApp.bus().register(this); + super.onStart(); + } + + @Override + protected void onStop() { + super.onStop(); + MainApp.bus().unregister(this); + } + + public LongSparseArray getAutosensDataTable() { return autosensDataTable; } - public static List getBucketedData() { + public List getBucketedData() { return bucketed_data; } - @Override - public int getType() { - return GENERAL; - } - - @Override - public String getFragmentClass() { - return null; - } - - @Override - public String getName() { - return "IOB COB Calculator"; - } - - @Override - public String getNameShort() { - return "IOC"; - } - - @Override - public boolean isEnabled(int type) { - return type == GENERAL; - } - - @Override - public boolean isVisibleInTabs(int type) { - return false; - } - - @Override - public boolean canBeHidden(int type) { - return false; - } - - @Override - public boolean hasFragment() { - return false; - } - - @Override - public boolean showInList(int type) { - return false; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - - } - - @Override - public int getPreferencesId() { - return -1; - } - - IobCobCalculatorPlugin() { - MainApp.bus().register(this); - } - @Nullable - public static List getBucketedData(long fromTime) { + public List getBucketedData(long fromTime) { //log.debug("Locking getBucketedData"); synchronized (dataLock) { if (bucketed_data == null) { @@ -152,7 +118,7 @@ public class IobCobCalculatorPlugin implements PluginBase { return null; } - private static int indexNewerThan(long time) { + private int indexNewerThan(long time) { for (int index = 0; index < bucketed_data.size(); index++) { if (bucketed_data.get(index).date < time) return index - 1; @@ -167,9 +133,9 @@ public class IobCobCalculatorPlugin implements PluginBase { return rouded; } - void loadBgData() { - bgReadings = MainApp.getDbHelper().getBgreadingsDataFromTime((long) (System.currentTimeMillis() - 60 * 60 * 1000L * (24 + dia)), false); - log.debug("BG data loaded. Size: " + bgReadings.size()); + void loadBgData(long start) { + bgReadings = MainApp.getDbHelper().getBgreadingsDataFromTime((long) (start - 60 * 60 * 1000L * (24 + dia)), false); + log.debug("BG data loaded. Size: " + bgReadings.size() + " Start date: " + DateUtil.dateAndTimeString(start)); } private boolean isAbout5minData() { @@ -259,7 +225,7 @@ public class IobCobCalculatorPlugin implements PluginBase { } - public void createBucketedData5min() { + private void createBucketedData5min() { if (bgReadings == null || bgReadings.size() < 3) { bucketed_data = null; return; @@ -321,22 +287,22 @@ public class IobCobCalculatorPlugin implements PluginBase { log.debug("Bucketed data created. Size: " + bucketed_data.size()); } - public static long oldestDataAvailable() { + public long oldestDataAvailable() { long now = System.currentTimeMillis(); - long oldestDataAvailable = MainApp.getConfigBuilder().oldestDataAvailable(); + long oldestDataAvailable = TreatmentsPlugin.getPlugin().oldestDataAvailable(); long getBGDataFrom = Math.max(oldestDataAvailable, (long) (now - 60 * 60 * 1000L * (24 + MainApp.getConfigBuilder().getProfile().getDia()))); log.debug("Limiting data to oldest available temps: " + new Date(oldestDataAvailable).toString()); return getBGDataFrom; } - public static IobTotal calculateFromTreatmentsAndTempsSynchronized(long time) { + public IobTotal calculateFromTreatmentsAndTempsSynchronized(long time, Profile profile) { synchronized (dataLock) { - return calculateFromTreatmentsAndTemps(time); + return calculateFromTreatmentsAndTemps(time, profile); } } - public static IobTotal calculateFromTreatmentsAndTemps(long time) { + public IobTotal calculateFromTreatmentsAndTemps(long time, Profile profile) { long now = System.currentTimeMillis(); time = roundUpTime(time); if (time < now && iobTable.get(time) != null) { @@ -345,8 +311,22 @@ public class IobCobCalculatorPlugin implements PluginBase { } else { //log.debug(">>> calculateFromTreatmentsAndTemps Cache miss " + new Date(time).toLocaleString()); } - IobTotal bolusIob = MainApp.getConfigBuilder().getCalculationToTimeTreatments(time).round(); - IobTotal basalIob = MainApp.getConfigBuilder().getCalculationToTimeTempBasals(time).round(); + IobTotal bolusIob = TreatmentsPlugin.getPlugin().getCalculationToTimeTreatments(time).round(); + IobTotal basalIob = TreatmentsPlugin.getPlugin().getCalculationToTimeTempBasals(time, profile, true, now).round(); + if (OpenAPSSMBPlugin.getPlugin().isEnabled(PluginType.APS)) { + // Add expected zero temp basal for next 240 mins + IobTotal basalIobWithZeroTemp = basalIob.copy(); + TemporaryBasal t = new TemporaryBasal() + .date(now + 60 * 1000L) + .duration(240) + .absolute(0); + if (t.date < time) { + IobTotal calc = t.iobCalc(time, profile); + basalIobWithZeroTemp.plus(calc); + } + + basalIob.iobWithZeroTemp = basalIobWithZeroTemp; + } IobTotal iobTotal = IobTotal.combine(bolusIob, basalIob).round(); if (time < System.currentTimeMillis()) { @@ -356,7 +336,7 @@ public class IobCobCalculatorPlugin implements PluginBase { } @Nullable - private static Long findPreviousTimeFromBucketedData(long time) { + private Long findPreviousTimeFromBucketedData(long time) { if (bucketed_data == null) return null; for (int index = 0; index < bucketed_data.size(); index++) { @@ -366,17 +346,18 @@ public class IobCobCalculatorPlugin implements PluginBase { return null; } - public static BasalData getBasalData(long time) { + public BasalData getBasalData(long time) { long now = System.currentTimeMillis(); time = roundUpTime(time); BasalData retval = basalDataTable.get(time); if (retval == null) { retval = new BasalData(); - TemporaryBasal tb = MainApp.getConfigBuilder().getTempBasalFromHistory(time); - retval.basal = MainApp.getConfigBuilder().getProfile(time).getBasal(time); + Profile profile = MainApp.getConfigBuilder().getProfile(time); + TemporaryBasal tb = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(time); + retval.basal = profile.getBasal(time); if (tb != null) { retval.isTempBasalRunning = true; - retval.tempBasalAbsolute = tb.tempBasalConvertedToAbsolute(time); + retval.tempBasalAbsolute = tb.tempBasalConvertedToAbsolute(time, profile); } else { retval.isTempBasalRunning = false; retval.tempBasalAbsolute = retval.basal; @@ -392,7 +373,7 @@ public class IobCobCalculatorPlugin implements PluginBase { } @Nullable - public static AutosensData getAutosensData(long time) { + public AutosensData getAutosensData(long time) { synchronized (dataLock) { long now = System.currentTimeMillis(); if (time > now) @@ -417,20 +398,43 @@ public class IobCobCalculatorPlugin implements PluginBase { } @Nullable - public static AutosensData getLastAutosensDataSynchronized(String reason) { + public AutosensData getLastAutosensDataSynchronized(String reason) { synchronized (dataLock) { return getLastAutosensData(reason); } } + public CobInfo getCobInfo(boolean _synchronized, String reason) { + AutosensData autosensData = _synchronized ? getLastAutosensDataSynchronized(reason) : getLastAutosensData(reason); + Double displayCob = null; + double futureCarbs = 0; + long now = now(); + List treatments = TreatmentsPlugin.getPlugin().getTreatmentsFromHistory(); + + if (autosensData != null) { + displayCob = autosensData.cob; + for (Treatment treatment : treatments) { + if (IobCobCalculatorPlugin.roundUpTime(treatment.date) > IobCobCalculatorPlugin.roundUpTime(autosensData.time) + && treatment.date <= now && treatment.carbs > 0) { + displayCob += treatment.carbs; + } + } + } + for (Treatment treatment : treatments) { + if (treatment.date > now && treatment.carbs > 0) { + futureCarbs += treatment.carbs; + } + } + return new CobInfo(displayCob, futureCarbs); + } @Nullable - public static AutosensData getLastAutosensData(String reason) { + public AutosensData getLastAutosensData(String reason) { if (autosensDataTable.size() < 1) { log.debug("AUTOSENSDATA null: autosensDataTable empty (" + reason + ")"); return null; } - AutosensData data = null; + AutosensData data; try { data = autosensDataTable.valueAt(autosensDataTable.size() - 1); } catch (Exception e) { @@ -444,14 +448,11 @@ public class IobCobCalculatorPlugin implements PluginBase { log.debug("AUTOSENSDATA null: data is old (" + reason + ") size()=" + autosensDataTable.size() + " lastdata=" + DateUtil.dateAndTimeString(data.time)); return null; } else { - if (data == null) - log.debug("AUTOSENSDATA null: data == null (" + " " + reason + ") size()=" + autosensDataTable.size() + " lastdata=" + DateUtil.dateAndTimeString(data.time)); return data; } } - public static IobTotal[] calculateIobArrayInDia() { - Profile profile = MainApp.getConfigBuilder().getProfile(); + public IobTotal[] calculateIobArrayInDia(Profile profile) { // predict IOB out to DIA plus 30m long time = System.currentTimeMillis(); time = roundUpTime(time); @@ -460,14 +461,30 @@ public class IobCobCalculatorPlugin implements PluginBase { int pos = 0; for (int i = 0; i < len; i++) { long t = time + i * 5 * 60000; - IobTotal iob = calculateFromTreatmentsAndTempsSynchronized(t); + IobTotal iob = calculateFromTreatmentsAndTempsSynchronized(t, profile); array[pos] = iob; pos++; } return array; } - public static AutosensResult detectSensitivityWithLock(long fromTime, long toTime) { + public IobTotal[] calculateIobArrayForSMB(Profile profile) { + // predict IOB out to DIA plus 30m + long time = System.currentTimeMillis(); + time = roundUpTime(time); + int len = (4 * 60) / 5; + IobTotal[] array = new IobTotal[len]; + int pos = 0; + for (int i = 0; i < len; i++) { + long t = time + i * 5 * 60000; + IobTotal iob = calculateFromTreatmentsAndTempsSynchronized(t, profile); + array[pos] = iob; + pos++; + } + return array; + } + + public AutosensResult detectSensitivityWithLock(long fromTime, long toTime) { synchronized (dataLock) { return detectSensitivity(fromTime, toTime); } @@ -486,14 +503,24 @@ public class IobCobCalculatorPlugin implements PluginBase { } @Subscribe + @SuppressWarnings("unused") public void onEventAppInitialized(EventAppInitialized ev) { - runCalculation("onEventAppInitialized", true); + if (this != getPlugin()) { + log.debug("Ignoring event for non default instance"); + return; + } + runCalculation("onEventAppInitialized", System.currentTimeMillis(), true, ev); } @Subscribe + @SuppressWarnings("unused") public void onEventNewBG(EventNewBG ev) { + if (this != getPlugin()) { + log.debug("Ignoring event for non default instance"); + return; + } stopCalculation("onEventNewBG"); - runCalculation("onEventNewBG", true); + runCalculation("onEventNewBG", System.currentTimeMillis(), true, ev); } private void stopCalculation(String from) { @@ -507,16 +534,20 @@ public class IobCobCalculatorPlugin implements PluginBase { } } - private void runCalculation(String from, boolean bgDataReload) { + public void runCalculation(String from, long start, boolean bgDataReload, Event cause) { log.debug("Starting calculation thread: " + from); if (thread == null || thread.getState() == Thread.State.TERMINATED) { - thread = new IobCobThread(this, from, bgDataReload); + thread = new IobCobThread(this, from, start, bgDataReload, cause); thread.start(); } } @Subscribe public void onNewProfile(EventNewBasalProfile ev) { + if (this != getPlugin()) { + log.debug("Ignoring event for non default instance"); + return; + } if (MainApp.getConfigBuilder() == null) return; // app still initializing Profile profile = MainApp.getConfigBuilder().getProfile(); @@ -532,14 +563,20 @@ public class IobCobCalculatorPlugin implements PluginBase { iobTable = new LongSparseArray<>(); autosensDataTable = new LongSparseArray<>(); } - runCalculation("onNewProfile", false); + runCalculation("onNewProfile", System.currentTimeMillis(), false, ev); } @Subscribe public void onEventPreferenceChange(EventPreferenceChange ev) { + if (this != getPlugin()) { + log.debug("Ignoring event for non default instance"); + return; + } if (ev.isChanged(R.string.key_openapsama_autosens_period) || ev.isChanged(R.string.key_age) || - ev.isChanged(R.string.key_absorption_maxtime) + ev.isChanged(R.string.key_absorption_maxtime) || + ev.isChanged(R.string.key_openapsama_min_5m_carbimpact) || + ev.isChanged(R.string.key_absorption_cutoff) ) { stopCalculation("onEventPreferenceChange"); synchronized (dataLock) { @@ -547,24 +584,32 @@ public class IobCobCalculatorPlugin implements PluginBase { iobTable = new LongSparseArray<>(); autosensDataTable = new LongSparseArray<>(); } - runCalculation("onEventPreferenceChange", false); + runCalculation("onEventPreferenceChange", System.currentTimeMillis(), false, ev); } } @Subscribe public void onEventConfigBuilderChange(EventConfigBuilderChange ev) { + if (this != getPlugin()) { + log.debug("Ignoring event for non default instance"); + return; + } stopCalculation("onEventConfigBuilderChange"); synchronized (dataLock) { log.debug("Invalidating cached data because of configuration change. IOB: " + iobTable.size() + " Autosens: " + autosensDataTable.size() + " records"); iobTable = new LongSparseArray<>(); autosensDataTable = new LongSparseArray<>(); } - runCalculation("onEventConfigBuilderChange", false); + runCalculation("onEventConfigBuilderChange", System.currentTimeMillis(), false, ev); } // When historical data is changed (comming from NS etc) finished calculations after this date must be invalidated @Subscribe public void onEventNewHistoryData(EventNewHistoryData ev) { + if (this != getPlugin()) { + log.debug("Ignoring event for non default instance"); + return; + } //log.debug("Locking onNewHistoryData"); stopCalculation("onEventNewHistoryData"); synchronized (dataLock) { @@ -599,10 +644,18 @@ public class IobCobCalculatorPlugin implements PluginBase { } } } - runCalculation("onEventNewHistoryData", false); + runCalculation("onEventNewHistoryData", System.currentTimeMillis(), false, ev); //log.debug("Releasing onNewHistoryData"); } + public void clearCache() { + synchronized (dataLock) { + log.debug("Clearing cached data."); + iobTable = new LongSparseArray<>(); + autosensDataTable = new LongSparseArray<>(); + } + } + // From https://gist.github.com/IceCreamYou/6ffa1b18c4c8f6aeaad2 // Returns the value at a given percentile in a sorted numeric array. // "Linear interpolation between closest ranks" method diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobThread.java b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobThread.java index 947e6ba03f..c37730b227 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobThread.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobThread.java @@ -4,6 +4,8 @@ import android.content.Context; import android.os.PowerManager; import android.support.v4.util.LongSparseArray; +import com.crashlytics.android.answers.CustomEvent; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,39 +13,52 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.BgReading; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.events.Event; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventAutosensCalculationFinished; -import info.nightscout.androidaps.queue.QueueThread; +import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventIobCalculationProgress; +import info.nightscout.androidaps.plugins.OpenAPSSMB.SMBDefaults; +import info.nightscout.androidaps.plugins.SensitivityAAPS.SensitivityAAPSPlugin; +import info.nightscout.androidaps.plugins.SensitivityWeightedAverage.SensitivityWeightedAveragePlugin; +import info.nightscout.androidaps.plugins.Treatments.Treatment; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.FabricPrivacy; +import info.nightscout.utils.SP; -import static info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin.getBucketedData; -import static info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin.oldestDataAvailable; -import static info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin.roundUpTime; +import static info.nightscout.utils.DateUtil.now; /** * Created by mike on 23.01.2018. */ public class IobCobThread extends Thread { - private static Logger log = LoggerFactory.getLogger(QueueThread.class); + private static Logger log = LoggerFactory.getLogger(IobCobThread.class); + private final Event cause; private IobCobCalculatorPlugin iobCobCalculatorPlugin; private boolean bgDataReload; private String from; + private long start; private PowerManager.WakeLock mWakeLock; - public IobCobThread(IobCobCalculatorPlugin plugin, String from, boolean bgDataReload) { + public IobCobThread(IobCobCalculatorPlugin plugin, String from, long start, boolean bgDataReload, Event cause) { super(); this.iobCobCalculatorPlugin = plugin; this.bgDataReload = bgDataReload; this.from = from; + this.cause = cause; + this.start = start; PowerManager powerManager = (PowerManager) MainApp.instance().getApplicationContext().getSystemService(Context.POWER_SERVICE); mWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "iobCobThread"); @@ -57,34 +72,34 @@ public class IobCobThread extends Thread { log.debug("Aborting calculation thread (ConfigBuilder not ready): " + from); return; // app still initializing } - if (MainApp.getConfigBuilder().getProfile() == null) { + if (!MainApp.getConfigBuilder().isProfileValid("IobCobThread")) { log.debug("Aborting calculation thread (No profile): " + from); return; // app still initializing } //log.debug("Locking calculateSensitivityData"); - Object dataLock = iobCobCalculatorPlugin.dataLock; + long oldestTimeWithData = iobCobCalculatorPlugin.oldestDataAvailable(); - long oldestTimeWithData = oldestDataAvailable(); - - synchronized (dataLock) { + synchronized (iobCobCalculatorPlugin.dataLock) { if (bgDataReload) { - iobCobCalculatorPlugin.loadBgData(); + iobCobCalculatorPlugin.loadBgData(start); iobCobCalculatorPlugin.createBucketedData(); } - List bucketed_data = getBucketedData(); - LongSparseArray autosensDataTable = iobCobCalculatorPlugin.getAutosensDataTable(); + List bucketed_data = iobCobCalculatorPlugin.getBucketedData(); + LongSparseArray autosensDataTable = IobCobCalculatorPlugin.getPlugin().getAutosensDataTable(); if (bucketed_data == null || bucketed_data.size() < 3) { log.debug("Aborting calculation thread (No bucketed data available): " + from); return; } - long prevDataTime = roundUpTime(bucketed_data.get(bucketed_data.size() - 3).date); + long prevDataTime = IobCobCalculatorPlugin.roundUpTime(bucketed_data.get(bucketed_data.size() - 3).date); log.debug("Prev data time: " + new Date(prevDataTime).toLocaleString()); AutosensData previous = autosensDataTable.get(prevDataTime); // start from oldest to be able sub cob for (int i = bucketed_data.size() - 4; i >= 0; i--) { + MainApp.bus().post(new EventIobCalculationProgress(i + "/" + bucketed_data.size())); + if (iobCobCalculatorPlugin.stopCalculationTrigger) { iobCobCalculatorPlugin.stopCalculationTrigger = false; log.debug("Aborting calculation thread (trigger): " + from); @@ -92,10 +107,9 @@ public class IobCobThread extends Thread { } // check if data already exists long bgTime = bucketed_data.get(i).date; - bgTime = roundUpTime(bgTime); - if (bgTime > System.currentTimeMillis()) + bgTime = IobCobCalculatorPlugin.roundUpTime(bgTime); + if (bgTime > IobCobCalculatorPlugin.roundUpTime(now())) continue; - Profile profile = MainApp.getConfigBuilder().getProfile(bgTime); AutosensData existing; if ((existing = autosensDataTable.get(bgTime)) != null) { @@ -103,16 +117,12 @@ public class IobCobThread extends Thread { continue; } + Profile profile = MainApp.getConfigBuilder().getProfile(bgTime); if (profile == null) { log.debug("Aborting calculation thread (no profile): " + from); return; // profile not set yet } - if (profile.getIsf(bgTime) == null) { - log.debug("Aborting calculation thread (no ISF): " + from); - return; // profile not set yet - } - if (Config.logAutosensData) log.debug("Processing calculation thread: " + from + " (" + i + "/" + bucketed_data.size() + ")"); @@ -135,13 +145,59 @@ public class IobCobThread extends Thread { continue; } delta = (bg - bucketed_data.get(i + 1).value); + avgDelta = (bg - bucketed_data.get(i + 3).value) / 3; - IobTotal iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime); + IobTotal iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile); double bgi = -iob.activity * sens * 5; double deviation = delta - bgi; + double avgDeviation = Math.round((avgDelta - bgi) * 1000) / 1000; - List recentTreatments = MainApp.getConfigBuilder().getTreatments5MinBackFromHistory(bgTime); + double slopeFromMaxDeviation = 0; + double slopeFromMinDeviation = 999; + double maxDeviation = 0; + double minDeviation = 999; + + // https://github.com/openaps/oref0/blob/master/lib/determine-basal/cob-autosens.js#L169 + if (i < bucketed_data.size() - 16) { // we need 1h of data to calculate minDeviationSlope + long hourago = bgTime + 10 * 1000 - 60 * 60 * 1000L; + AutosensData hourAgoData = IobCobCalculatorPlugin.getPlugin().getAutosensData(hourago); + if (hourAgoData != null) { + int initialIndex = autosensDataTable.indexOfKey(hourAgoData.time); + if (Config.logAutosensData) + log.debug(">>>>> bucketed_data.size()=" + bucketed_data.size() + " i=" + i + "hourAgoData=" + hourAgoData.toString()); + int past = 1; + try { + for (; past < 12; past++) { + AutosensData ad = autosensDataTable.valueAt(initialIndex + past); + double deviationSlope = (ad.avgDeviation - avgDeviation) / (ad.time - bgTime) * 1000 * 60 * 5; + if (ad.avgDeviation > maxDeviation) { + slopeFromMaxDeviation = Math.min(0, deviationSlope); + maxDeviation = ad.avgDeviation; + } + if (ad.avgDeviation < minDeviation) { + slopeFromMinDeviation = Math.max(0, deviationSlope); + minDeviation = ad.avgDeviation; + } + + //if (Config.logAutosensData) + // log.debug("Deviations: " + new Date(bgTime) + new Date(ad.time) + " avgDeviation=" + avgDeviation + " deviationSlope=" + deviationSlope + " slopeFromMaxDeviation=" + slopeFromMaxDeviation + " slopeFromMinDeviation=" + slopeFromMinDeviation); + } + } catch (Exception e) { + log.error("Unhandled exception", e); + FabricPrivacy.logException(e); + FabricPrivacy.getInstance().logCustom(new CustomEvent("CatchedError") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION) + .putCustomAttribute("autosensDataTable", iobCobCalculatorPlugin.getAutosensDataTable().toString()) + .putCustomAttribute("for_data", ">>>>> bucketed_data.size()=" + bucketed_data.size() + " i=" + i + "hourAgoData=" + hourAgoData.toString()) + .putCustomAttribute("past", past) + ); + } + } + } + + List recentTreatments = TreatmentsPlugin.getPlugin().getTreatments5MinBackFromHistory(bgTime); for (int ir = 0; ir < recentTreatments.size(); ir++) { autosensData.carbsFromBolus += recentTreatments.get(ir).carbs; autosensData.activeCarbsList.add(new AutosensData.CarbsInPast(recentTreatments.get(ir))); @@ -152,24 +208,38 @@ public class IobCobThread extends Thread { if (previous != null && previous.cob > 0) { // calculate sum of min carb impact from all active treatments double totalMinCarbsImpact = 0d; - for (int ii = 0; ii < autosensData.activeCarbsList.size(); ++ii) { - AutosensData.CarbsInPast c = autosensData.activeCarbsList.get(ii); - totalMinCarbsImpact += c.min5minCarbImpact; + if (SensitivityAAPSPlugin.getPlugin().isEnabled(PluginType.SENSITIVITY) || SensitivityWeightedAveragePlugin.getPlugin().isEnabled(PluginType.SENSITIVITY)) { + //when the impact depends on a max time, sum them up as smaller carb sizes make them smaller + for (int ii = 0; ii < autosensData.activeCarbsList.size(); ++ii) { + AutosensData.CarbsInPast c = autosensData.activeCarbsList.get(ii); + totalMinCarbsImpact += c.min5minCarbImpact; + } + } else { + //Oref sensitivity + totalMinCarbsImpact = SP.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact); } // figure out how many carbs that represents // but always assume at least 3mg/dL/5m (default) absorption per active treatment double ci = Math.max(deviation, totalMinCarbsImpact); + if (ci != deviation) + autosensData.failoverToMinAbsorbtionRate = true; autosensData.absorbed = ci * profile.getIc(bgTime) / sens; // and add that to the running total carbsAbsorbed autosensData.cob = Math.max(previous.cob - autosensData.absorbed, 0d); autosensData.substractAbosorbedCarbs(); + autosensData.usedMinCarbsImpact = totalMinCarbsImpact; } autosensData.removeOldCarbs(bgTime); autosensData.cob += autosensData.carbsFromBolus; autosensData.deviation = deviation; autosensData.bgi = bgi; autosensData.delta = delta; + autosensData.avgDelta = avgDelta; + autosensData.avgDeviation = avgDeviation; + autosensData.slopeFromMaxDeviation = slopeFromMaxDeviation; + autosensData.slopeFromMinDeviation = slopeFromMinDeviation; + // calculate autosens only without COB if (autosensData.cob <= 0) { @@ -191,15 +261,18 @@ public class IobCobThread extends Thread { previous = autosensData; autosensDataTable.put(bgTime, autosensData); + if (Config.logAutosensData) + log.debug("Running detectSensitivity from: " + DateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + DateUtil.dateAndTimeString(bgTime)); autosensData.autosensRatio = iobCobCalculatorPlugin.detectSensitivity(oldestTimeWithData, bgTime).ratio; if (Config.logAutosensData) - log.debug(autosensData.log(bgTime)); + log.debug(autosensData.toString()); } } - MainApp.bus().post(new EventAutosensCalculationFinished()); + MainApp.bus().post(new EventAutosensCalculationFinished(cause)); log.debug("Finishing calculation thread: " + from); } finally { mWakeLock.release(); + MainApp.bus().post(new EventIobCalculationProgress("")); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/events/EventAutosensCalculationFinished.java b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/events/EventAutosensCalculationFinished.java index ea73915436..2cb9b7ca4c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/events/EventAutosensCalculationFinished.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/events/EventAutosensCalculationFinished.java @@ -1,5 +1,6 @@ package info.nightscout.androidaps.plugins.IobCobCalculator.events; +import info.nightscout.androidaps.events.Event; import info.nightscout.androidaps.events.EventLoop; /** @@ -7,4 +8,9 @@ import info.nightscout.androidaps.events.EventLoop; */ public class EventAutosensCalculationFinished extends EventLoop { + public Event cause; + + public EventAutosensCalculationFinished(Event cause) { + this.cause = cause; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/events/EventIobCalculationProgress.java b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/events/EventIobCalculationProgress.java new file mode 100644 index 0000000000..08f2747e60 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/events/EventIobCalculationProgress.java @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.plugins.IobCobCalculator.events; + +import info.nightscout.androidaps.events.Event; + +public class EventIobCalculationProgress extends Event { + public String progress; + + public EventIobCalculationProgress(String progress) { + this.progress = progress; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Loop/APSResult.java b/app/src/main/java/info/nightscout/androidaps/plugins/Loop/APSResult.java index f8ca0ab8c4..7a9c4deae9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Loop/APSResult.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Loop/APSResult.java @@ -1,17 +1,23 @@ package info.nightscout.androidaps.plugins.Loop; -import android.os.Parcel; -import android.os.Parcelable; import android.text.Html; import android.text.Spanned; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.IobTotal; +import info.nightscout.androidaps.db.BgReading; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.utils.DecimalFormatter; @@ -22,36 +28,69 @@ import info.nightscout.utils.DecimalFormatter; public class APSResult { private static Logger log = LoggerFactory.getLogger(APSResult.class); + public Date date; public String reason; public double rate; public int duration; - public boolean changeRequested = false; + public boolean tempBasalRequested = false; + public boolean bolusRequested = false; + public IobTotal iob; + public JSONObject json = new JSONObject(); + public boolean hasPredictions = false; + public double smb = 0d; // super micro bolus in units + public long deliverAt = 0; + + public Constraint inputConstraints; + + public Constraint rateConstraint; + public Constraint smbConstraint; @Override public String toString() { final PumpInterface pump = ConfigBuilderPlugin.getActivePump(); - if (changeRequested) { + if (isChangeRequested()) { + String ret; + // rate if (rate == 0 && duration == 0) - return MainApp.sResources.getString(R.string.canceltemp); + ret = MainApp.sResources.getString(R.string.canceltemp) + "\n"; + else if (rate == -1) + ret = MainApp.sResources.getString(R.string.let_temp_basal_run) + "\n"; else - return MainApp.sResources.getString(R.string.rate) + ": " + DecimalFormatter.to2Decimal(rate) + " U/h " + - "(" + DecimalFormatter.to2Decimal(rate / pump.getBaseBasalRate() * 100) + "%)\n" + - MainApp.sResources.getString(R.string.duration) + ": " + DecimalFormatter.to0Decimal(duration) + " min\n" + - MainApp.sResources.getString(R.string.reason) + ": " + reason; + ret = MainApp.sResources.getString(R.string.rate) + ": " + DecimalFormatter.to2Decimal(rate) + " U/h " + + "(" + DecimalFormatter.to2Decimal(rate / pump.getBaseBasalRate() * 100) + "%) \n" + + MainApp.sResources.getString(R.string.duration) + ": " + DecimalFormatter.to2Decimal(duration) + " min\n"; + + // smb + if (smb != 0) + ret += ("SMB: " + DecimalFormatter.toPumpSupportedBolus(smb) + " U\n"); + + // reason + ret += MainApp.sResources.getString(R.string.reason) + ": " + reason; + return ret; } else return MainApp.sResources.getString(R.string.nochangerequested); } public Spanned toSpanned() { final PumpInterface pump = ConfigBuilderPlugin.getActivePump(); - if (changeRequested) { - String ret = ""; - if (rate == 0 && duration == 0) ret = MainApp.sResources.getString(R.string.canceltemp); + if (isChangeRequested()) { + String ret; + // rate + if (rate == 0 && duration == 0) + ret = MainApp.sResources.getString(R.string.canceltemp) + "
"; + else if (rate == -1) + ret = MainApp.sResources.getString(R.string.let_temp_basal_run) + "
"; else ret = "" + MainApp.sResources.getString(R.string.rate) + ": " + DecimalFormatter.to2Decimal(rate) + " U/h " + "(" + DecimalFormatter.to2Decimal(rate / pump.getBaseBasalRate() * 100) + "%)
" + - "" + MainApp.sResources.getString(R.string.duration) + ": " + DecimalFormatter.to2Decimal(duration) + " min
" + - "" + MainApp.sResources.getString(R.string.reason) + ": " + reason.replace("<", "<").replace(">", ">"); + "" + MainApp.sResources.getString(R.string.duration) + ": " + DecimalFormatter.to2Decimal(duration) + " min
"; + + // smb + if (smb != 0) + ret += ("" + "SMB" + ": " + DecimalFormatter.toPumpSupportedBolus(smb) + " U
"); + + // reason + ret += "" + MainApp.sResources.getString(R.string.reason) + ": " + reason.replace("<", "<").replace(">", ">"); return Html.fromHtml(ret); } else return Html.fromHtml(MainApp.sResources.getString(R.string.nochangerequested)); @@ -62,17 +101,25 @@ public class APSResult { public APSResult clone() { APSResult newResult = new APSResult(); - newResult.reason = new String(reason); + newResult.reason = reason; newResult.rate = rate; newResult.duration = duration; - newResult.changeRequested = changeRequested; + newResult.tempBasalRequested = tempBasalRequested; + newResult.bolusRequested = bolusRequested; + newResult.iob = iob; + newResult.json = json; + newResult.hasPredictions = hasPredictions; + newResult.smb = smb; + newResult.deliverAt = deliverAt; + newResult.rateConstraint = rateConstraint; + newResult.smbConstraint = smbConstraint; return newResult; } public JSONObject json() { JSONObject json = new JSONObject(); try { - if (changeRequested) { + if (isChangeRequested()) { json.put("rate", rate); json.put("duration", duration); json.put("reason", reason); @@ -82,4 +129,105 @@ public class APSResult { } return json; } + + public List getPredictions() { + List array = new ArrayList<>(); + try { + long startTime = date.getTime(); + if (json.has("predBGs")) { + JSONObject predBGs = json.getJSONObject("predBGs"); + if (predBGs.has("IOB")) { + JSONArray iob = predBGs.getJSONArray("IOB"); + for (int i = 1; i < iob.length(); i++) { + BgReading bg = new BgReading(); + bg.value = iob.getInt(i); + bg.date = startTime + i * 5 * 60 * 1000L; + bg.isIOBPrediction = true; + array.add(bg); + } + } + if (predBGs.has("aCOB")) { + JSONArray iob = predBGs.getJSONArray("aCOB"); + for (int i = 1; i < iob.length(); i++) { + BgReading bg = new BgReading(); + bg.value = iob.getInt(i); + bg.date = startTime + i * 5 * 60 * 1000L; + bg.isaCOBPrediction = true; + array.add(bg); + } + } + if (predBGs.has("COB")) { + JSONArray iob = predBGs.getJSONArray("COB"); + for (int i = 1; i < iob.length(); i++) { + BgReading bg = new BgReading(); + bg.value = iob.getInt(i); + bg.date = startTime + i * 5 * 60 * 1000L; + bg.isCOBPrediction = true; + array.add(bg); + } + } + if (predBGs.has("UAM")) { + JSONArray iob = predBGs.getJSONArray("UAM"); + for (int i = 1; i < iob.length(); i++) { + BgReading bg = new BgReading(); + bg.value = iob.getInt(i); + bg.date = startTime + i * 5 * 60 * 1000L; + bg.isUAMPrediction = true; + array.add(bg); + } + } + if (predBGs.has("ZT")) { + JSONArray iob = predBGs.getJSONArray("ZT"); + for (int i = 1; i < iob.length(); i++) { + BgReading bg = new BgReading(); + bg.value = iob.getInt(i); + bg.date = startTime + i * 5 * 60 * 1000L; + bg.isZTPrediction = true; + array.add(bg); + } + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + return array; + } + + public long getLatestPredictionsTime() { + long latest = 0; + try { + long startTime = date != null ? date.getTime() : 0; + if (json.has("predBGs")) { + JSONObject predBGs = json.getJSONObject("predBGs"); + if (predBGs.has("IOB")) { + JSONArray iob = predBGs.getJSONArray("IOB"); + latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); + } + if (predBGs.has("aCOB")) { + JSONArray iob = predBGs.getJSONArray("aCOB"); + latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); + } + if (predBGs.has("COB")) { + JSONArray iob = predBGs.getJSONArray("COB"); + latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); + } + if (predBGs.has("UAM")) { + JSONArray iob = predBGs.getJSONArray("UAM"); + latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); + } + if (predBGs.has("ZT")) { + JSONArray iob = predBGs.getJSONArray("ZT"); + latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + + return latest; + } + + public boolean isChangeRequested() { + return tempBasalRequested || bolusRequested; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Loop/LoopFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Loop/LoopFragment.java index 1b696988b2..5285dc7831 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Loop/LoopFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Loop/LoopFragment.java @@ -3,36 +3,51 @@ package info.nightscout.androidaps.plugins.Loop; import android.app.Activity; import android.os.Bundle; +import android.text.Html; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import com.squareup.otto.Subscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; import info.nightscout.androidaps.plugins.Loop.events.EventLoopSetLastRunGui; import info.nightscout.androidaps.plugins.Loop.events.EventLoopUpdateGui; +import info.nightscout.utils.FabricPrivacy; -public class LoopFragment extends SubscriberFragment implements View.OnClickListener { +public class LoopFragment extends SubscriberFragment { private static Logger log = LoggerFactory.getLogger(LoopFragment.class); + @BindView(R.id.loop_run) Button runNowButton; + @BindView(R.id.loop_lastrun) TextView lastRunView; + @BindView(R.id.loop_lastenact) TextView lastEnactView; + @BindView(R.id.loop_source) TextView sourceView; + @BindView(R.id.loop_request) TextView requestView; + @BindView(R.id.loop_constraintsprocessed) TextView constraintsProcessedView; - TextView setByPumpView; + @BindView(R.id.loop_constraints) + TextView constraintsView; + @BindView(R.id.loop_tbrsetbypump) + TextView tbrSetByPumpView; + @BindView(R.id.loop_smbsetbypump) + TextView smbSetByPumpView; @Override @@ -40,41 +55,19 @@ public class LoopFragment extends SubscriberFragment implements View.OnClickList Bundle savedInstanceState) { try { View view = inflater.inflate(R.layout.loop_fragment, container, false); - - lastRunView = (TextView) view.findViewById(R.id.loop_lastrun); - lastEnactView = (TextView) view.findViewById(R.id.loop_lastenact); - sourceView = (TextView) view.findViewById(R.id.loop_source); - requestView = (TextView) view.findViewById(R.id.loop_request); - constraintsProcessedView = (TextView) view.findViewById(R.id.loop_constraintsprocessed); - setByPumpView = (TextView) view.findViewById(R.id.loop_setbypump); - runNowButton = (Button) view.findViewById(R.id.loop_run); - runNowButton.setOnClickListener(this); - - updateGUI(); + unbinder = ButterKnife.bind(this, view); return view; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } - return null; } - @Override - public void onClick(View view) { - switch (view.getId()) { - case R.id.loop_run: - lastRunView.setText(MainApp.sResources.getString(R.string.executing)); - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - LoopPlugin.getPlugin().invoke("Loop button", true); - } - }); - thread.start(); - Answers.getInstance().logCustom(new CustomEvent("Loop_Run")); - break; - } - + @OnClick(R.id.loop_run) + void onRunClick() { + lastRunView.setText(MainApp.sResources.getString(R.string.executing)); + new Thread(() -> LoopPlugin.getPlugin().invoke("Loop button", true)).start(); + FabricPrivacy.getInstance().logCustom(new CustomEvent("Loop_Run")); } @Subscribe @@ -87,12 +80,7 @@ public class LoopFragment extends SubscriberFragment implements View.OnClickList clearGUI(); final Activity activity = getActivity(); if (activity != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - lastRunView.setText(ev.text); - } - }); + activity.runOnUiThread(() -> lastRunView.setText(ev.text)); } @@ -100,17 +88,27 @@ public class LoopFragment extends SubscriberFragment implements View.OnClickList protected void updateGUI() { Activity activity = getActivity(); if (activity != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - if (LoopPlugin.lastRun != null) { - requestView.setText(LoopPlugin.lastRun.request != null ? LoopPlugin.lastRun.request.toSpanned() : ""); - constraintsProcessedView.setText(LoopPlugin.lastRun.constraintsProcessed != null ? LoopPlugin.lastRun.constraintsProcessed.toSpanned() : ""); - setByPumpView.setText(LoopPlugin.lastRun.setByPump != null ? LoopPlugin.lastRun.setByPump.toSpanned() : ""); - sourceView.setText(LoopPlugin.lastRun.source != null ? LoopPlugin.lastRun.source : ""); - lastRunView.setText(LoopPlugin.lastRun.lastAPSRun != null && LoopPlugin.lastRun.lastAPSRun.getTime() != 0 ? LoopPlugin.lastRun.lastAPSRun.toLocaleString() : ""); - lastEnactView.setText(LoopPlugin.lastRun.lastEnact != null && LoopPlugin.lastRun.lastEnact.getTime() != 0 ? LoopPlugin.lastRun.lastEnact.toLocaleString() : ""); + activity.runOnUiThread(() -> { + LoopPlugin.LastRun lastRun = LoopPlugin.lastRun; + if (lastRun != null) { + requestView.setText(lastRun.request != null ? lastRun.request.toSpanned() : ""); + constraintsProcessedView.setText(lastRun.constraintsProcessed != null ? lastRun.constraintsProcessed.toSpanned() : ""); + sourceView.setText(lastRun.source != null ? lastRun.source : ""); + lastRunView.setText(lastRun.lastAPSRun != null && lastRun.lastAPSRun.getTime() != 0 ? lastRun.lastAPSRun.toLocaleString() : ""); + lastEnactView.setText(lastRun.lastEnact != null && lastRun.lastEnact.getTime() != 0 ? lastRun.lastEnact.toLocaleString() : ""); + tbrSetByPumpView.setText(lastRun.tbrSetByPump != null ? Html.fromHtml(lastRun.tbrSetByPump.toHtml()) : ""); + smbSetByPumpView.setText(lastRun.smbSetByPump != null ? Html.fromHtml(lastRun.smbSetByPump.toHtml()) : ""); + + String constraints = ""; + if (lastRun.constraintsProcessed != null) { + Constraint allConstraints = new Constraint<>(0d); + if (lastRun.constraintsProcessed.rateConstraint != null) + allConstraints.copyReasons(lastRun.constraintsProcessed.rateConstraint); + if (lastRun.constraintsProcessed.smbConstraint != null) + allConstraints.copyReasons(lastRun.constraintsProcessed.smbConstraint); + constraints = allConstraints.getMostLimitedReasons(); } + constraintsView.setText(constraints); } }); } @@ -118,16 +116,14 @@ public class LoopFragment extends SubscriberFragment implements View.OnClickList void clearGUI() { Activity activity = getActivity(); if (activity != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - requestView.setText(""); - constraintsProcessedView.setText(""); - setByPumpView.setText(""); - sourceView.setText(""); - lastRunView.setText(""); - lastEnactView.setText(""); - } + activity.runOnUiThread(() -> { + requestView.setText(""); + constraintsProcessedView.setText(""); + sourceView.setText(""); + lastRunView.setText(""); + lastEnactView.setText(""); + tbrSetByPumpView.setText(""); + smbSetByPumpView.setText(""); }); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Loop/LoopPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Loop/LoopPlugin.java index b2d79f4b7e..43d00b24af 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Loop/LoopPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Loop/LoopPlugin.java @@ -1,14 +1,18 @@ package info.nightscout.androidaps.plugins.Loop; +import android.annotation.SuppressLint; import android.app.Notification; +import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.TaskStackBuilder; import android.content.Context; import android.content.Intent; +import android.os.Build; +import android.os.SystemClock; +import android.support.annotation.NonNull; import android.support.v4.app.NotificationCompat; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import com.squareup.otto.Subscribe; @@ -22,29 +26,43 @@ import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainActivity; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.db.BgReading; +import info.nightscout.androidaps.db.DatabaseHelper; +import info.nightscout.androidaps.events.Event; import info.nightscout.androidaps.events.EventNewBG; import info.nightscout.androidaps.events.EventTreatmentChange; import info.nightscout.androidaps.interfaces.APSInterface; -import info.nightscout.androidaps.interfaces.ConstraintsInterface; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventAutosensCalculationFinished; import info.nightscout.androidaps.plugins.Loop.events.EventLoopSetLastRunGui; import info.nightscout.androidaps.plugins.Loop.events.EventLoopUpdateGui; import info.nightscout.androidaps.plugins.Loop.events.EventNewOpenLoopNotification; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.androidaps.queue.Callback; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.NSUpload; import info.nightscout.utils.SP; /** * Created by mike on 05.08.2016. */ -public class LoopPlugin implements PluginBase { +public class LoopPlugin extends PluginBase { private static Logger log = LoggerFactory.getLogger(LoopPlugin.class); - private static LoopPlugin loopPlugin; + public static final String CHANNEL_ID = "AndroidAPS-Openloop"; + long lastBgTriggeredRun = 0; + + protected static LoopPlugin loopPlugin; + + @NonNull public static LoopPlugin getPlugin() { if (loopPlugin == null) { loopPlugin = new LoopPlugin(); @@ -52,9 +70,6 @@ public class LoopPlugin implements PluginBase { return loopPlugin; } - private boolean fragmentEnabled = false; - private boolean fragmentVisible = false; - private long loopSuspendedTill = 0L; // end of manual loop suspend private boolean isSuperBolus = false; private boolean isDisconnected = false; @@ -62,7 +77,8 @@ public class LoopPlugin implements PluginBase { public class LastRun { public APSResult request = null; public APSResult constraintsProcessed = null; - public PumpEnactResult setByPump = null; + public PumpEnactResult tbrSetByPump = null; + public PumpEnactResult smbSetByPump = null; public String source = null; public Date lastAPSRun = null; public Date lastEnact = null; @@ -72,88 +88,76 @@ public class LoopPlugin implements PluginBase { static public LastRun lastRun = null; public LoopPlugin() { - MainApp.bus().register(this); + super(new PluginDescription() + .mainType(PluginType.LOOP) + .fragmentClass(LoopFragment.class.getName()) + .pluginName(R.string.loop) + .shortName(R.string.loop_shortname) + .preferencesId(R.xml.pref_closedmode) + ); loopSuspendedTill = SP.getLong("loopSuspendedTill", 0L); isSuperBolus = SP.getBoolean("isSuperBolus", false); isDisconnected = SP.getBoolean("isDisconnected", false); } @Override - public String getFragmentClass() { - return LoopFragment.class.getName(); + protected void onStart() { + MainApp.bus().register(this); + createNotificationChannel(); + super.onStart(); } - @Override - public int getType() { - return PluginBase.LOOP; - } + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - @Override - public String getName() { - return MainApp.instance().getString(R.string.loop); - } - - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.loop_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; + NotificationManager mNotificationManager = + (NotificationManager) MainApp.instance().getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE); + @SuppressLint("WrongConstant") NotificationChannel channel = new NotificationChannel(CHANNEL_ID, + CHANNEL_ID, + NotificationManager.IMPORTANCE_HIGH); + mNotificationManager.createNotificationChannel(channel); } - // use long name as fallback - return getName(); } @Override - public boolean isEnabled(int type) { - boolean pumpCapable = ConfigBuilderPlugin.getActivePump() == null || ConfigBuilderPlugin.getActivePump().getPumpDescription().isTempBasalCapable; - return type == LOOP && fragmentEnabled && pumpCapable; + protected void onStop() { + super.onStop(); + MainApp.bus().unregister(this); } @Override - public boolean isVisibleInTabs(int type) { - boolean pumpCapable = ConfigBuilderPlugin.getActivePump() == null || ConfigBuilderPlugin.getActivePump().getPumpDescription().isTempBasalCapable; - return type == LOOP && fragmentVisible && pumpCapable; + public boolean specialEnableCondition() { + PumpInterface pump = ConfigBuilderPlugin.getActivePump(); + return pump == null || pump.getPumpDescription().isTempBasalCapable; } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == LOOP) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == LOOP) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return R.xml.pref_closedmode; - } - + + /** + * This method is triggered once autosens calculation has completed, so the LoopPlugin + * has current data to work with. However, autosens calculation can be triggered by multiple + * sources and currently only a new BG should trigger a loop run. Hence we return early if + * the event causing the calculation is not EventNewBg. + * + * Callers of {@link info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin#runCalculation(String, long, boolean, Event)} + * are sources triggering a calculation which triggers this method upon completion. + */ @Subscribe - public void onStatusEvent(final EventTreatmentChange ev) { - invoke("EventTreatmentChange", true); - } + public void onStatusEvent(final EventAutosensCalculationFinished ev) { + if (!(ev.cause instanceof EventNewBG)) { + // Autosens calculation not triggered by a new BG + return; + } + BgReading bgReading = DatabaseHelper.actualBg(); + if (bgReading == null) { + // BG outdated + return; + } + if (bgReading.date <= lastBgTriggeredRun) { + // already looped with that value + return; + } - @Subscribe - public void onStatusEvent(final EventNewBG ev) { - invoke("EventNewBG", true); + lastBgTriggeredRun = bgReading.date; + invoke("AutosenseCalculation for " + bgReading, true); } public long suspendedTo() { @@ -243,23 +247,31 @@ public class LoopPlugin implements PluginBase { return isDisconnected; } - public void invoke(String initiator, boolean allowNotification) { + public synchronized void invoke(String initiator, boolean allowNotification){ + invoke(initiator, allowNotification, false); + } + + public synchronized void invoke(String initiator, boolean allowNotification, boolean tempBasalFallback) { try { if (Config.logFunctionCalls) log.debug("invoke from " + initiator); - ConstraintsInterface constraintsInterface = MainApp.getConfigBuilder(); - if (!constraintsInterface.isLoopEnabled()) { - log.debug(MainApp.sResources.getString(R.string.loopdisabled)); - MainApp.bus().post(new EventLoopSetLastRunGui(MainApp.sResources.getString(R.string.loopdisabled))); + Constraint loopEnabled = MainApp.getConstraintChecker().isLoopInvokationAllowed(); + + if (!loopEnabled.value()) { + String message = MainApp.sResources.getString(R.string.loopdisabled) + "\n" + loopEnabled.getReasons(); + log.debug(message); + MainApp.bus().post(new EventLoopSetLastRunGui(message)); return; } final PumpInterface pump = ConfigBuilderPlugin.getActivePump(); APSResult result = null; - if (!isEnabled(PluginBase.LOOP)) + if (!isEnabled(PluginType.LOOP)) return; - if (MainApp.getConfigBuilder().getProfile() == null) { + Profile profile = MainApp.getConfigBuilder().getProfile(); + + if (!MainApp.getConfigBuilder().isProfileValid("Loop")) { log.debug(MainApp.sResources.getString(R.string.noprofileselected)); MainApp.bus().post(new EventLoopSetLastRunGui(MainApp.sResources.getString(R.string.noprofileselected))); return; @@ -269,8 +281,8 @@ public class LoopPlugin implements PluginBase { if (pump.getBaseBasalRate() < 0.01d) return; APSInterface usedAPS = ConfigBuilderPlugin.getActiveAPS(); - if (usedAPS != null && ((PluginBase) usedAPS).isEnabled(PluginBase.APS)) { - usedAPS.invoke(initiator); + if (usedAPS != null && ((PluginBase) usedAPS).isEnabled(PluginType.APS)) { + usedAPS.invoke(initiator, tempBasalFallback); result = usedAPS.getLastAPSResult(); } @@ -282,14 +294,25 @@ public class LoopPlugin implements PluginBase { // check rate for constrais final APSResult resultAfterConstraints = result.clone(); - resultAfterConstraints.rate = constraintsInterface.applyBasalConstraints(resultAfterConstraints.rate); + resultAfterConstraints.rateConstraint = new Constraint<>(resultAfterConstraints.rate); + resultAfterConstraints.rate = MainApp.getConstraintChecker().applyBasalConstraints(resultAfterConstraints.rateConstraint, profile).value(); + resultAfterConstraints.smbConstraint = new Constraint<>(resultAfterConstraints.smb); + resultAfterConstraints.smb = MainApp.getConstraintChecker().applyBolusConstraints(resultAfterConstraints.smbConstraint).value(); + + // safety check for multiple SMBs + long lastBolusTime = TreatmentsPlugin.getPlugin().getLastBolusTime(); + if (lastBolusTime != 0 && lastBolusTime + 3 * 60 * 1000 > System.currentTimeMillis()) { + log.debug("SMB requsted but still in 3 min interval"); + resultAfterConstraints.smb = 0; + } if (lastRun == null) lastRun = new LastRun(); lastRun.request = result; lastRun.constraintsProcessed = resultAfterConstraints; lastRun.lastAPSRun = new Date(); lastRun.source = ((PluginBase) usedAPS).getName(); - lastRun.setByPump = null; + lastRun.tbrSetByPump = null; + lastRun.smbSetByPump = null; NSUpload.uploadDeviceStatus(); @@ -305,34 +328,53 @@ public class LoopPlugin implements PluginBase { return; } - if (constraintsInterface.isClosedModeEnabled()) { - if (result.changeRequested) { + Constraint closedLoopEnabled = MainApp.getConstraintChecker().isClosedLoopAllowed(); + + if (closedLoopEnabled.value()) { + if (result.isChangeRequested()) { final PumpEnactResult waiting = new PumpEnactResult(); - final PumpEnactResult previousResult = lastRun.setByPump; waiting.queued = true; - lastRun.setByPump = waiting; + if (resultAfterConstraints.tempBasalRequested) + lastRun.tbrSetByPump = waiting; + if (resultAfterConstraints.bolusRequested) + lastRun.smbSetByPump = waiting; MainApp.bus().post(new EventLoopUpdateGui()); - MainApp.getConfigBuilder().applyAPSRequest(resultAfterConstraints, new Callback() { + FabricPrivacy.getInstance().logCustom(new CustomEvent("APSRequest")); + MainApp.getConfigBuilder().applyTBRRequest(resultAfterConstraints, profile, new Callback() { @Override public void run() { - Answers.getInstance().logCustom(new CustomEvent("APSRequest")); if (result.enacted || result.success) { - lastRun.setByPump = result; + lastRun.tbrSetByPump = result; lastRun.lastEnact = lastRun.lastAPSRun; - } else { - lastRun.setByPump = previousResult; + MainApp.getConfigBuilder().applySMBRequest(resultAfterConstraints, new Callback() { + @Override + public void run() { + //Callback is only called if a bolus was acutally requested + if (result.enacted || result.success) { + lastRun.smbSetByPump = result; + lastRun.lastEnact = lastRun.lastAPSRun; + } else { + new Thread(() -> { + SystemClock.sleep(1000); + LoopPlugin.getPlugin().invoke("tempBasalFallback", allowNotification, true); + }).start(); + FabricPrivacy.getInstance().logCustom(new CustomEvent("Loop_Run_TempBasalFallback")); + } + MainApp.bus().post(new EventLoopUpdateGui()); + } + }); } MainApp.bus().post(new EventLoopUpdateGui()); } }); } else { - lastRun.setByPump = null; - lastRun.source = null; + lastRun.tbrSetByPump = null; + lastRun.smbSetByPump = null; } } else { - if (result.changeRequested && allowNotification) { + if (result.isChangeRequested() && allowNotification) { NotificationCompat.Builder builder = - new NotificationCompat.Builder(MainApp.instance().getApplicationContext()); + new NotificationCompat.Builder(MainApp.instance().getApplicationContext(), CHANNEL_ID); builder.setSmallIcon(R.drawable.notif_icon) .setContentTitle(MainApp.sResources.getString(R.string.openloop_newsuggestion)) .setContentText(resultAfterConstraints.toString()) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/NSClientInternalFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/NSClientFragment.java similarity index 79% rename from app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/NSClientInternalFragment.java rename to app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/NSClientFragment.java index 8cd9432f81..39e24f8b38 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/NSClientInternalFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/NSClientFragment.java @@ -7,7 +7,6 @@ import android.content.Context; import android.content.DialogInterface; import android.graphics.Paint; import android.os.Bundle; -import android.support.v4.app.Fragment; import android.text.Html; import android.text.Spanned; import android.view.LayoutInflater; @@ -18,8 +17,6 @@ import android.widget.CompoundButton; import android.widget.ScrollView; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import com.squareup.otto.Subscribe; @@ -33,10 +30,11 @@ import info.nightscout.androidaps.plugins.Common.SubscriberFragment; import info.nightscout.androidaps.plugins.NSClientInternal.events.EventNSClientNewLog; import info.nightscout.androidaps.plugins.NSClientInternal.events.EventNSClientRestart; import info.nightscout.androidaps.plugins.NSClientInternal.events.EventNSClientUpdateGUI; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.SP; -public class NSClientInternalFragment extends SubscriberFragment implements View.OnClickListener, CompoundButton.OnCheckedChangeListener { - private static Logger log = LoggerFactory.getLogger(NSClientInternalFragment.class); +public class NSClientFragment extends SubscriberFragment implements View.OnClickListener, CompoundButton.OnCheckedChangeListener { + private static Logger log = LoggerFactory.getLogger(NSClientFragment.class); private TextView logTextView; private TextView queueTextView; @@ -59,10 +57,10 @@ public class NSClientInternalFragment extends SubscriberFragment implements View logScrollview = (ScrollView) view.findViewById(R.id.nsclientinternal_logscrollview); autoscrollCheckbox = (CheckBox) view.findViewById(R.id.nsclientinternal_autoscroll); - autoscrollCheckbox.setChecked(NSClientInternalPlugin.getPlugin().autoscroll); + autoscrollCheckbox.setChecked(NSClientPlugin.getPlugin().autoscroll); autoscrollCheckbox.setOnCheckedChangeListener(this); pausedCheckbox = (CheckBox) view.findViewById(R.id.nsclientinternal_paused); - pausedCheckbox.setChecked(NSClientInternalPlugin.getPlugin().paused); + pausedCheckbox.setChecked(NSClientPlugin.getPlugin().paused); pausedCheckbox.setOnCheckedChangeListener(this); logTextView = (TextView) view.findViewById(R.id.nsclientinternal_log); queueTextView = (TextView) view.findViewById(R.id.nsclientinternal_queue); @@ -88,7 +86,7 @@ public class NSClientInternalFragment extends SubscriberFragment implements View updateGUI(); return view; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } return null; @@ -99,14 +97,14 @@ public class NSClientInternalFragment extends SubscriberFragment implements View switch (view.getId()) { case R.id.nsclientinternal_restart: MainApp.bus().post(new EventNSClientRestart()); - Answers.getInstance().logCustom(new CustomEvent("NSClientRestart")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("NSClientRestart")); break; case R.id.nsclientinternal_delivernow: - NSClientInternalPlugin.getPlugin().resend("GUI"); - Answers.getInstance().logCustom(new CustomEvent("NSClientDeliverNow")); + NSClientPlugin.getPlugin().resend("GUI"); + FabricPrivacy.getInstance().logCustom(new CustomEvent("NSClientDeliverNow")); break; case R.id.nsclientinternal_clearlog: - NSClientInternalPlugin.getPlugin().clearLog(); + NSClientPlugin.getPlugin().clearLog(); break; case R.id.nsclientinternal_clearqueue: final Context context = getContext(); @@ -118,15 +116,15 @@ public class NSClientInternalFragment extends SubscriberFragment implements View public void onClick(DialogInterface dialog, int id) { UploadQueue.clearQueue(); updateGUI(); - Answers.getInstance().logCustom(new CustomEvent("NSClientClearQueue")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("NSClientClearQueue")); } }); builder.setNegativeButton(getString(R.string.cancel), null); builder.show(); break; case R.id.nsclientinternal_showqueue: - MainApp.bus().post(new EventNSClientNewLog("QUEUE", NSClientInternalPlugin.getPlugin().queue().textList())); - Answers.getInstance().logCustom(new CustomEvent("NSClientShowQueue")); + MainApp.bus().post(new EventNSClientNewLog("QUEUE", NSClientPlugin.getPlugin().queue().textList())); + FabricPrivacy.getInstance().logCustom(new CustomEvent("NSClientShowQueue")); break; } } @@ -136,14 +134,14 @@ public class NSClientInternalFragment extends SubscriberFragment implements View switch (buttonView.getId()) { case R.id.nsclientinternal_paused: SP.putBoolean(R.string.key_nsclientinternal_paused, isChecked); - NSClientInternalPlugin.getPlugin().paused = isChecked; + NSClientPlugin.getPlugin().paused = isChecked; MainApp.bus().post(new EventPreferenceChange(R.string.key_nsclientinternal_paused)); updateGUI(); - Answers.getInstance().logCustom(new CustomEvent("NSClientPause")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("NSClientPause")); break; case R.id.nsclientinternal_autoscroll: SP.putBoolean(R.string.key_nsclientinternal_autoscroll, isChecked); - NSClientInternalPlugin.getPlugin().autoscroll = isChecked; + NSClientPlugin.getPlugin().autoscroll = isChecked; updateGUI(); break; } @@ -161,15 +159,15 @@ public class NSClientInternalFragment extends SubscriberFragment implements View activity.runOnUiThread(new Runnable() { @Override public void run() { - NSClientInternalPlugin.updateLog(); - logTextView.setText(NSClientInternalPlugin.textLog); - if (NSClientInternalPlugin.getPlugin().autoscroll) { + NSClientPlugin.getPlugin().updateLog(); + logTextView.setText(NSClientPlugin.getPlugin().textLog); + if (NSClientPlugin.getPlugin().autoscroll) { logScrollview.fullScroll(ScrollView.FOCUS_DOWN); } - urlTextView.setText(NSClientInternalPlugin.getPlugin().url()); + urlTextView.setText(NSClientPlugin.getPlugin().url()); Spanned queuetext = Html.fromHtml(MainApp.sResources.getString(R.string.queue) + " " + UploadQueue.size() + ""); queueTextView.setText(queuetext); - statusTextView.setText(NSClientInternalPlugin.getPlugin().status); + statusTextView.setText(NSClientPlugin.getPlugin().status); } }); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/NSClientInternalPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/NSClientPlugin.java similarity index 53% rename from app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/NSClientInternalPlugin.java rename to app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/NSClientPlugin.java index a51ea18cc7..0525a3a9dd 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/NSClientInternalPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/NSClientPlugin.java @@ -18,126 +18,90 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; +import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.events.EventAppExit; +import info.nightscout.androidaps.events.EventNetworkChange; +import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.plugins.NSClientInternal.events.EventNSClientNewLog; import info.nightscout.androidaps.plugins.NSClientInternal.events.EventNSClientStatus; import info.nightscout.androidaps.plugins.NSClientInternal.events.EventNSClientUpdateGUI; import info.nightscout.androidaps.plugins.NSClientInternal.services.NSClientService; +import info.nightscout.androidaps.receivers.NetworkChangeReceiver; import info.nightscout.utils.SP; import info.nightscout.utils.ToastUtils; -public class NSClientInternalPlugin implements PluginBase { - private static Logger log = LoggerFactory.getLogger(NSClientInternalPlugin.class); +public class NSClientPlugin extends PluginBase { + private static Logger log = LoggerFactory.getLogger(NSClientPlugin.class); - static NSClientInternalPlugin nsClientInternalPlugin; + static NSClientPlugin nsClientPlugin; - static public NSClientInternalPlugin getPlugin() { - if (nsClientInternalPlugin == null) { - nsClientInternalPlugin = new NSClientInternalPlugin(); + static public NSClientPlugin getPlugin() { + if (nsClientPlugin == null) { + nsClientPlugin = new NSClientPlugin(); } - return nsClientInternalPlugin; + return nsClientPlugin; } - private boolean fragmentEnabled = true; - private boolean fragmentVisible = true; + public Handler handler; - static public Handler handler; - - private static List listLog = new ArrayList<>(); - static Spanned textLog = Html.fromHtml(""); + private final List listLog = new ArrayList<>(); + Spanned textLog = Html.fromHtml(""); public boolean paused = false; + public boolean allowed = true; boolean autoscroll = true; public String status = ""; public NSClientService nsClientService = null; - NSClientInternalPlugin() { - MainApp.bus().register(this); + private NSClientPlugin() { + super(new PluginDescription() + .mainType(PluginType.GENERAL) + .fragmentClass(NSClientFragment.class.getName()) + .pluginName(R.string.nsclientinternal) + .shortName(R.string.nsclientinternal_shortname) + .preferencesId(R.xml.pref_nsclientinternal) + ); + + if (Config.NSCLIENT || Config.G5UPLOADER) { + pluginDescription.alwaysEnabled(true).visibleByDefault(true); + } paused = SP.getBoolean(R.string.key_nsclientinternal_paused, false); autoscroll = SP.getBoolean(R.string.key_nsclientinternal_autoscroll, true); if (handler == null) { - HandlerThread handlerThread = new HandlerThread(NSClientInternalPlugin.class.getSimpleName() + "Handler"); + HandlerThread handlerThread = new HandlerThread(NSClientPlugin.class.getSimpleName() + "Handler"); handlerThread.start(); handler = new Handler(handlerThread.getLooper()); } + } + @Override + protected void onStart() { + MainApp.bus().register(this); Context context = MainApp.instance().getApplicationContext(); Intent intent = new Intent(context, NSClientService.class); context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + super.onStart(); + + EventNetworkChange event = NetworkChangeReceiver.grabNetworkStatus(); + if (event != null) + MainApp.bus().post(event); } @Override - public int getType() { - return PluginBase.GENERAL; - } - - @Override - public String getFragmentClass() { - return NSClientInternalFragment.class.getName(); - } - - @Override - public String getName() { - return MainApp.sResources.getString(R.string.nsclientinternal); - } - - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.nsclientinternal_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == GENERAL && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == GENERAL && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return !Config.NSCLIENT && !Config.G5UPLOADER; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == GENERAL) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == GENERAL) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return R.xml.pref_nsclientinternal; + protected void onStop() { + MainApp.bus().unregister(this); + Context context = MainApp.instance().getApplicationContext(); + context.unbindService(mConnection); } private ServiceConnection mConnection = new ServiceConnection() { @@ -150,13 +114,44 @@ public class NSClientInternalPlugin implements PluginBase { public void onServiceConnected(ComponentName name, IBinder service) { log.debug("Service is connected"); NSClientService.LocalBinder mLocalBinder = (NSClientService.LocalBinder) service; - nsClientService = mLocalBinder.getServiceInstance(); + if (mLocalBinder != null) // is null when running in roboelectric + nsClientService = mLocalBinder.getServiceInstance(); } }; - @SuppressWarnings("UnusedParameters") @Subscribe - public void onStatusEvent(final EventAppExit e) { + public void onStatusEvent(EventPreferenceChange ev) { + if (ev.isChanged(R.string.key_ns_wifionly) || + ev.isChanged(R.string.key_ns_wifi_ssids) || + ev.isChanged(R.string.key_ns_allowroaming) + ) { + EventNetworkChange event = NetworkChangeReceiver.grabNetworkStatus(); + if (event != null) + MainApp.bus().post(event); + } + } + + @Subscribe + public void onStatusEvent(final EventNetworkChange ev) { + boolean wifiOnly = SP.getBoolean(R.string.key_ns_wifionly, false); + String allowedSSIDs = SP.getString(R.string.key_ns_wifi_ssids, ""); + boolean allowRoaming = SP.getBoolean(R.string.key_ns_allowroaming, true) && !wifiOnly; + + boolean newAllowedState = true; + + if (!ev.wifiConnected && wifiOnly) newAllowedState = false; + if (ev.wifiConnected && !allowedSSIDs.isEmpty() && !allowedSSIDs.contains(ev.ssid)) + newAllowedState = false; + if (!allowRoaming && ev.roaming) newAllowedState = false; + + if (newAllowedState != allowed) { + allowed = newAllowedState; + MainApp.bus().post(new EventPreferenceChange(R.string.key_nsclientinternal_paused)); + } + } + + @Subscribe + public void onStatusEvent(final EventAppExit ignored) { if (nsClientService != null) MainApp.instance().getApplicationContext().unbindService(mConnection); } @@ -177,7 +172,9 @@ public class NSClientInternalPlugin implements PluginBase { handler.post(new Runnable() { @Override public void run() { - listLog = new ArrayList<>(); + synchronized (listLog) { + listLog.clear(); + } MainApp.bus().post(new EventNSClientUpdateGUI()); } }); @@ -187,22 +184,25 @@ public class NSClientInternalPlugin implements PluginBase { handler.post(new Runnable() { @Override public void run() { - listLog.add(ev); - // remove the first line if log is too large - if (listLog.size() >= Constants.MAX_LOG_LINES) { - listLog.remove(0); + synchronized (listLog) { + listLog.add(ev); + // remove the first line if log is too large + if (listLog.size() >= Constants.MAX_LOG_LINES) { + listLog.remove(0); + } } MainApp.bus().post(new EventNSClientUpdateGUI()); } }); } - static synchronized void updateLog() { + synchronized void updateLog() { try { StringBuilder newTextLog = new StringBuilder(); - List temporaryList = new ArrayList<>(listLog); - for (EventNSClientNewLog log : temporaryList) { - newTextLog.append(log.toPreparedHtml()); + synchronized (listLog) { + for (EventNSClientNewLog log : listLog) { + newTextLog.append(log.toPreparedHtml()); + } } textLog = Html.fromHtml(newTextLog.toString()); } catch (OutOfMemoryError e) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/UploadQueue.java b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/UploadQueue.java index 889128abdc..17cc1dced3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/UploadQueue.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/UploadQueue.java @@ -48,7 +48,7 @@ public class UploadQueue { public void run() { log.debug("QUEUE adding: " + dbr.data); MainApp.getDbHelper().create(dbr); - NSClientInternalPlugin plugin = MainApp.getSpecificPlugin(NSClientInternalPlugin.class); + NSClientPlugin plugin = NSClientPlugin.getPlugin(); if (plugin != null) { plugin.resend("newdata"); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/data/NSDeviceStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/data/NSDeviceStatus.java index 315e9724ce..d1755dd4c1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/data/NSDeviceStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/data/NSDeviceStatus.java @@ -124,7 +124,7 @@ public class NSDeviceStatus { static DeviceStatusPumpData deviceStatusPumpData = null; public Spanned getExtendedPumpStatus() { - if (deviceStatusPumpData.extended != null) + if (deviceStatusPumpData != null && deviceStatusPumpData.extended != null) return deviceStatusPumpData.extended; return Html.fromHtml(""); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/data/NSSettingsStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/data/NSSettingsStatus.java index 3feced94cd..8d09a05146 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/data/NSSettingsStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/data/NSSettingsStatus.java @@ -170,6 +170,19 @@ public class NSSettingsStatus { } + // valid property is "warn" or "urgent" + // plugings "iage" "sage" "cage" "pbage" + + public double getExtendedWarnValue(String plugin, String property, double defaultvalue) { + JSONObject extendedSettings = this.getExtendedSettings(); + if (extendedSettings == null) + return defaultvalue; + JSONObject pluginJson = extendedSettings.optJSONObject(plugin); + if (pluginJson == null) + return defaultvalue; + return pluginJson.optDouble(property, defaultvalue); + } + public String getActiveProfile() { return getStringOrNull("activeProfile"); } @@ -193,7 +206,7 @@ public class NSSettingsStatus { return result; } } - if (settingsO.has("alarmTimeagoWarnMins") && Objects.equals(what, "alarmTimeagoWarnMins")){ + if (settingsO.has("alarmTimeagoWarnMins") && Objects.equals(what, "alarmTimeagoWarnMins")) { Double result = settingsO.getDouble(what); return result; } @@ -206,7 +219,7 @@ public class NSSettingsStatus { private String getStringOrNull(String key) { String ret = null; - if(data == null) return null; + if (data == null) return null; if (data.has(key)) { try { ret = data.getString(key); @@ -288,21 +301,21 @@ public class NSSettingsStatus { JSONObject pump = extentendedPumpSettings(); switch (setting) { case "warnClock": - return pump != null && pump.has(setting) ? pump.getDouble(setting) : 30; + return pump != null && pump.has(setting) ? pump.getDouble(setting) : 30; case "urgentClock": - return pump != null && pump.has(setting) ? pump.getDouble(setting) : 30; + return pump != null && pump.has(setting) ? pump.getDouble(setting) : 30; case "warnRes": - return pump != null && pump.has(setting) ? pump.getDouble(setting) : 30; + return pump != null && pump.has(setting) ? pump.getDouble(setting) : 30; case "urgentRes": - return pump != null && pump.has(setting) ? pump.getDouble(setting) : 30; + return pump != null && pump.has(setting) ? pump.getDouble(setting) : 30; case "warnBattV": - return pump != null && pump.has(setting) ? pump.getDouble(setting) : 30; + return pump != null && pump.has(setting) ? pump.getDouble(setting) : 30; case "urgentBattV": - return pump != null && pump.has(setting) ? pump.getDouble(setting) : 30; - case "warnBattP": - return pump != null && pump.has(setting) ? pump.getDouble(setting) : 30; + return pump != null && pump.has(setting) ? pump.getDouble(setting) : 30; + case "warnBattP": + return pump != null && pump.has(setting) ? pump.getDouble(setting) : 30; case "urgentBattP": - return pump != null && pump.has(setting) ? pump.getDouble(setting) : 30; + return pump != null && pump.has(setting) ? pump.getDouble(setting) : 30; } } catch (JSONException e) { log.error("Unhandled exception", e); @@ -310,12 +323,12 @@ public class NSSettingsStatus { return 0d; } - + @Nullable public JSONObject extentendedPumpSettings() { try { JSONObject extended = getExtendedSettings(); - if(extended == null) return null; + if (extended == null) return null; if (extended.has("pump")) { JSONObject pump = extended.getJSONObject("pump"); return pump; @@ -350,7 +363,7 @@ public class NSSettingsStatus { return ""; } - public boolean openAPSEnabledAlerts() { + public boolean openAPSEnabledAlerts() { try { JSONObject pump = extentendedPumpSettings(); if (pump != null && pump.has("openaps")) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/receivers/AckAlarmReceiver.java b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/receivers/AckAlarmReceiver.java index 6bdf824b7c..2aae41538a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/receivers/AckAlarmReceiver.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/receivers/AckAlarmReceiver.java @@ -6,19 +6,14 @@ import android.content.Intent; import android.os.Bundle; import android.os.PowerManager; -import org.json.JSONException; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Date; - import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.db.DbRequest; import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.plugins.NSClientInternal.NSClientInternalPlugin; -import info.nightscout.androidaps.plugins.NSClientInternal.UploadQueue; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.plugins.NSClientInternal.NSClientPlugin; import info.nightscout.androidaps.plugins.NSClientInternal.data.AlarmAck; import info.nightscout.androidaps.plugins.NSClientInternal.services.NSClientService; import info.nightscout.utils.SP; @@ -32,8 +27,8 @@ public class AckAlarmReceiver extends BroadcastReceiver { PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, AckAlarmReceiver.class.getSimpleName()); - NSClientInternalPlugin nsClientInternalPlugin = MainApp.getSpecificPlugin(NSClientInternalPlugin.class); - if (!nsClientInternalPlugin.isEnabled(PluginBase.GENERAL)) { + NSClientPlugin nsClientPlugin = MainApp.getSpecificPlugin(NSClientPlugin.class); + if (!nsClientPlugin.isEnabled(PluginType.GENERAL)) { return; } if (SP.getBoolean(R.string.key_ns_noupload, false)) { @@ -53,7 +48,7 @@ public class AckAlarmReceiver extends BroadcastReceiver { ack.group = bundles.getString("group"); ack.silenceTime = bundles.getLong("silenceTime"); - NSClientService nsClientService = nsClientInternalPlugin.nsClientService; + NSClientService nsClientService = nsClientPlugin.nsClientService; if (nsClientService != null) nsClientService.sendAlarmAck(ack); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/receivers/DBAccessReceiver.java b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/receivers/DBAccessReceiver.java index c3cea9806a..332ccc2ea3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/receivers/DBAccessReceiver.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/receivers/DBAccessReceiver.java @@ -14,8 +14,8 @@ import org.slf4j.LoggerFactory; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.db.DbRequest; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.plugins.NSClientInternal.NSClientInternalPlugin; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.plugins.NSClientInternal.NSClientPlugin; import info.nightscout.androidaps.plugins.NSClientInternal.UploadQueue; import info.nightscout.androidaps.plugins.NSClientInternal.broadcasts.BroadcastTreatment; import info.nightscout.utils.DateUtil; @@ -94,7 +94,7 @@ public class DBAccessReceiver extends BroadcastReceiver { UploadQueue.add(dbr); } if (collection.equals("treatments")) { - genereateTreatmentOfflineBroadcast(dbr); + generateTreatmentOfflineBroadcast(dbr); } } @@ -105,11 +105,11 @@ public class DBAccessReceiver extends BroadcastReceiver { } public boolean shouldUpload() { - NSClientInternalPlugin nsClientInternalPlugin = MainApp.getSpecificPlugin(NSClientInternalPlugin.class); - return nsClientInternalPlugin.isEnabled(PluginBase.GENERAL) && !SP.getBoolean(R.string.key_ns_noupload, false); + NSClientPlugin nsClientPlugin = MainApp.getSpecificPlugin(NSClientPlugin.class); + return nsClientPlugin.isEnabled(PluginType.GENERAL) && !SP.getBoolean(R.string.key_ns_noupload, false); } - public void genereateTreatmentOfflineBroadcast(DbRequest request) { + public void generateTreatmentOfflineBroadcast(DbRequest request) { if (request.action.equals("dbAdd")) { try { JSONObject data = new JSONObject(request.data); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/services/NSClientService.java b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/services/NSClientService.java index 1ef71c2f75..445bef32fe 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/services/NSClientService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/services/NSClientService.java @@ -9,7 +9,7 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.PowerManager; -import com.crashlytics.android.Crashlytics; + import com.google.common.base.Charsets; import com.google.common.hash.Hashing; import com.j256.ormlite.dao.CloseableIterator; @@ -34,7 +34,8 @@ import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventConfigBuilderChange; import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.plugins.NSClientInternal.NSClientInternalPlugin; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.plugins.NSClientInternal.NSClientPlugin; import info.nightscout.androidaps.plugins.NSClientInternal.UploadQueue; import info.nightscout.androidaps.plugins.NSClientInternal.acks.NSAddAck; import info.nightscout.androidaps.plugins.NSClientInternal.acks.NSAuthAck; @@ -62,6 +63,8 @@ import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotificati import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.utils.DateUtil; +import info.nightscout.utils.FabricPrivacy; +import info.nightscout.utils.JsonHelper; import info.nightscout.utils.SP; import io.socket.client.IO; import io.socket.client.Socket; @@ -123,7 +126,7 @@ public class NSClientService extends Service { @Override public void onDestroy() { super.onDestroy(); - mWakeLock.release(); + if (mWakeLock.isHeld()) mWakeLock.release(); } public class LocalBinder extends Binder { @@ -178,7 +181,7 @@ public class NSClientService extends Service { @Subscribe public void onStatusEvent(EventConfigBuilderChange ev) { - if (nsEnabled != MainApp.getSpecificPlugin(NSClientInternalPlugin.class).isEnabled(PluginBase.GENERAL)) { + if (nsEnabled != MainApp.getSpecificPlugin(NSClientPlugin.class).isEnabled(PluginType.GENERAL)) { latestDateInReceivedData = 0; destroy(); initialize(); @@ -200,7 +203,10 @@ public class NSClientService extends Service { nsAPIhashCode = Hashing.sha1().hashString(nsAPISecret, Charsets.UTF_8).toString(); MainApp.bus().post(new EventNSClientStatus("Initializing")); - if (MainApp.getSpecificPlugin(NSClientInternalPlugin.class).paused) { + if (!MainApp.getSpecificPlugin(NSClientPlugin.class).allowed) { + MainApp.bus().post(new EventNSClientNewLog("NSCLIENT", "not allowed")); + MainApp.bus().post(new EventNSClientStatus("Not allowed")); + } else if (MainApp.getSpecificPlugin(NSClientPlugin.class).paused) { MainApp.bus().post(new EventNSClientNewLog("NSCLIENT", "paused")); MainApp.bus().post(new EventNSClientStatus("Paused")); } else if (!nsEnabled) { @@ -311,7 +317,7 @@ public class NSClientService extends Service { } public void readPreferences() { - nsEnabled = MainApp.getSpecificPlugin(NSClientInternalPlugin.class).isEnabled(PluginBase.GENERAL); + nsEnabled = MainApp.getSpecificPlugin(NSClientPlugin.class).isEnabled(PluginType.GENERAL); nsURL = SP.getString(R.string.key_nsclientinternal_url, ""); nsAPISecret = SP.getString(R.string.key_nsclientinternal_api_secret, ""); nsDevice = SP.getString("careportal_enteredby", ""); @@ -345,14 +351,14 @@ public class NSClientService extends Service { try { data = (JSONObject) args[0]; } catch (Exception e) { - Crashlytics.log("Wrong Announcement from NS: " + args[0]); + FabricPrivacy.log("Wrong Announcement from NS: " + args[0]); return; } if (Config.detailedLog) try { - MainApp.bus().post(new EventNSClientNewLog("ANNOUNCEMENT", data.has("message") ? data.getString("message") : "received")); + MainApp.bus().post(new EventNSClientNewLog("ANNOUNCEMENT", JsonHelper.safeGetString(data, "message", "received"))); } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } BroadcastAnnouncement.handleAnnouncement(data, getApplicationContext()); log.debug(data.toString()); @@ -381,7 +387,7 @@ public class NSClientService extends Service { try { data = (JSONObject) args[0]; } catch (Exception e) { - Crashlytics.log("Wrong alarm from NS: " + args[0]); + FabricPrivacy.log("Wrong alarm from NS: " + args[0]); return; } BroadcastAlarm.handleAlarm(data, getApplicationContext()); @@ -409,7 +415,7 @@ public class NSClientService extends Service { try { data = (JSONObject) args[0]; } catch (Exception e) { - Crashlytics.log("Wrong Urgent alarm from NS: " + args[0]); + FabricPrivacy.log("Wrong Urgent alarm from NS: " + args[0]); return; } if (Config.detailedLog) @@ -434,7 +440,7 @@ public class NSClientService extends Service { try { data = (JSONObject) args[0]; } catch (Exception e) { - Crashlytics.log("Wrong Urgent alarm from NS: " + args[0]); + FabricPrivacy.log("Wrong Urgent alarm from NS: " + args[0]); return; } if (Config.detailedLog) @@ -572,22 +578,18 @@ public class NSClientService extends Service { MainApp.bus().post(new EventNSClientNewLog("DATA", "received " + foods.length() + " foods")); for (Integer index = 0; index < foods.length(); index++) { JSONObject jsonFood = foods.getJSONObject(index); - NSTreatment treatment = new NSTreatment(jsonFood); // remove from upload queue if Ack is failing UploadQueue.removeID(jsonFood); - //Find latest date in treatment - if (treatment.getMills() != null && treatment.getMills() < System.currentTimeMillis()) - if (treatment.getMills() > latestDateInReceivedData) - latestDateInReceivedData = treatment.getMills(); - if (treatment.getAction() == null) { + String action = JsonHelper.safeGetString(jsonFood, "action"); + + if (action == null) { addedFoods.put(jsonFood); - } else if (treatment.getAction().equals("update")) { + } else if (action.equals("update")) { updatedFoods.put(jsonFood); - } else if (treatment.getAction().equals("remove")) { - if (treatment.getMills() != null && treatment.getMills() > System.currentTimeMillis() - 24 * 60 * 60 * 1000L) // handle 1 day old deletions only - removedFoods.put(jsonFood); + } else if (action.equals("remove")) { + removedFoods.put(jsonFood); } } if (removedFoods.length() > 0) { @@ -600,18 +602,6 @@ public class NSClientService extends Service { BroadcastFood.handleNewFood(addedFoods, MainApp.instance().getApplicationContext(), isDelta); } } - if (data.has("")) { - JSONArray foods = data.getJSONArray("food"); - if (foods.length() > 0) { - MainApp.bus().post(new EventNSClientNewLog("DATA", "received " + foods.length() + " foods")); - for (Integer index = 0; index < foods.length(); index++) { - JSONObject jsonFood = foods.getJSONObject(index); - // remove from upload queue if Ack is failing - UploadQueue.removeID(jsonFood); - } - BroadcastDeviceStatus.handleNewFoods(foods, MainApp.instance().getApplicationContext(), isDelta); - } - } if (data.has("mbgs")) { JSONArray mbgs = data.getJSONArray("mbgs"); if (mbgs.length() > 0) @@ -665,7 +655,7 @@ public class NSClientService extends Service { } //MainApp.bus().post(new EventNSClientNewLog("NSCLIENT", "onDataUpdate end"); } finally { - wakeLock.release(); + if (wakeLock.isHeld()) wakeLock.release(); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java index 466026e986..4c8de10b9c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java @@ -21,6 +21,7 @@ import java.lang.reflect.InvocationTargetException; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; @@ -29,6 +30,8 @@ import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.Loop.ScriptReader; import info.nightscout.androidaps.plugins.OpenAPSMA.LoggerCallback; +import info.nightscout.androidaps.plugins.OpenAPSSMB.SMBDefaults; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.SP; public class DetermineBasalAdapterAMAJS { @@ -189,8 +192,7 @@ public class DetermineBasalAdapterAMAJS { GlucoseStatus glucoseStatus, MealData mealData, double autosensDataRatio, - boolean tempTargetSet, - double min_5m_carbimpact) throws JSONException { + boolean tempTargetSet) throws JSONException { String units = profile.getUnits(); @@ -204,27 +206,34 @@ public class DetermineBasalAdapterAMAJS { mProfile.put("max_bg", maxBg); mProfile.put("target_bg", targetBg); mProfile.put("carb_ratio", profile.getIc()); - mProfile.put("sens", Profile.toMgdl(profile.getIsf().doubleValue(), units)); - mProfile.put("max_daily_safety_multiplier", SP.getInt("openapsama_max_daily_safety_multiplier", 3)); - mProfile.put("current_basal_safety_multiplier", SP.getInt("openapsama_current_basal_safety_multiplier", 4)); + mProfile.put("sens", Profile.toMgdl(profile.getIsf(), units)); + mProfile.put("max_daily_safety_multiplier", SP.getInt(R.string.key_openapsama_max_daily_safety_multiplier, 3)); + mProfile.put("current_basal_safety_multiplier", SP.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4d)); mProfile.put("skip_neutral_temps", true); mProfile.put("current_basal", basalrate); mProfile.put("temptargetSet", tempTargetSet); - mProfile.put("autosens_adjust_targets", SP.getBoolean("openapsama_autosens_adjusttargets", true)); - mProfile.put("min_5m_carbimpact", SP.getDouble("openapsama_min_5m_carbimpact", 3d)); + mProfile.put("autosens_adjust_targets", SP.getBoolean(R.string.key_openapsama_autosens_adjusttargets, true)); + //align with max-absorption model in AMA sensitivity + if(mealData.usedMinCarbsImpact > 0){ + mProfile.put("min_5m_carbimpact", mealData.usedMinCarbsImpact); + } else { + mProfile.put("min_5m_carbimpact", SP.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact)); + } if (units.equals(Constants.MMOL)) { mProfile.put("out_units", "mmol/L"); } - + + long now = System.currentTimeMillis(); + TemporaryBasal tb = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(now); mCurrentTemp = new JSONObject(); mCurrentTemp.put("temp", "absolute"); - mCurrentTemp.put("duration", MainApp.getConfigBuilder().getTempBasalRemainingMinutesFromHistory()); - mCurrentTemp.put("rate", MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()); + mCurrentTemp.put("duration", tb != null ? tb.getPlannedRemainingMinutes() : 0); + mCurrentTemp.put("rate", tb != null ? tb.tempBasalConvertedToAbsolute(now, profile) : 0d); // as we have non default temps longer than 30 mintues - TemporaryBasal tempBasal = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); + TemporaryBasal tempBasal = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); if (tempBasal != null) { mCurrentTemp.put("minutesrunning", tempBasal.getRealDuration()); } @@ -234,7 +243,7 @@ public class DetermineBasalAdapterAMAJS { mGlucoseStatus = new JSONObject(); mGlucoseStatus.put("glucose", glucoseStatus.glucose); - if (SP.getBoolean("always_use_shortavg", false)) { + if (SP.getBoolean(R.string.key_always_use_shortavg, false)) { mGlucoseStatus.put("delta", glucoseStatus.short_avgdelta); } else { mGlucoseStatus.put("delta", glucoseStatus.delta); @@ -247,18 +256,18 @@ public class DetermineBasalAdapterAMAJS { mMealData.put("boluses", mealData.boluses); mMealData.put("mealCOB", mealData.mealCOB); - if (MainApp.getConfigBuilder().isAMAModeEnabled()) { + if (MainApp.getConstraintChecker().isAutosensModeEnabled().value()) { mAutosensData = new JSONObject(); mAutosensData.put("ratio", autosensDataRatio); } else { - mAutosensData = null; + mAutosensData = null; } } public Object makeParam(JSONObject jsonObject, Context rhino, Scriptable scope) { - if(jsonObject == null) return Undefined.instance; + if (jsonObject == null) return Undefined.instance; Object param = NativeJSON.parse(rhino, scope, jsonObject.toString(), new Callable() { @Override @@ -270,8 +279,8 @@ public class DetermineBasalAdapterAMAJS { } public Object makeParamArray(JSONArray jsonArray, Context rhino, Scriptable scope) { - //Object param = NativeJSON.parse(rhino, scope, "{myarray: " + jsonArray.toString() + " }", new Callable() { - Object param = NativeJSON.parse(rhino, scope, jsonArray.toString(), new Callable() { + //Object param = NativeJSON.parse(rhino, scope, "{myarray: " + jsonArray.toString() + " }", new Callable() { + Object param = NativeJSON.parse(rhino, scope, jsonArray.toString(), new Callable() { @Override public Object call(Context context, Scriptable scriptable, Scriptable scriptable1, Object[] objects) { return objects[1]; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalResultAMA.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalResultAMA.java index e4c23ae043..cb2b4a0091 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalResultAMA.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalResultAMA.java @@ -1,35 +1,28 @@ package info.nightscout.androidaps.plugins.OpenAPSAMA; -import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.mozilla.javascript.NativeObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; import java.util.Date; -import java.util.List; -import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.plugins.Loop.APSResult; -import info.nightscout.androidaps.data.IobTotal; public class DetermineBasalResultAMA extends APSResult { private static Logger log = LoggerFactory.getLogger(DetermineBasalResultAMA.class); - public Date date; - public JSONObject json = new JSONObject(); public double eventualBG; public double snoozeBG; - public IobTotal iob; public DetermineBasalResultAMA(NativeObject result, JSONObject j) { + this(); date = new Date(); json = j; if (result.containsKey("error")) { reason = result.get("error").toString(); - changeRequested = false; + tempBasalRequested = false; rate = -1; duration = -1; } else { @@ -37,36 +30,37 @@ public class DetermineBasalResultAMA extends APSResult { if (result.containsKey("eventualBG")) eventualBG = (Double) result.get("eventualBG"); if (result.containsKey("snoozeBG")) snoozeBG = (Double) result.get("snoozeBG"); if (result.containsKey("rate")) { - rate = (Double) result.get("rate"); + rate = (Double) result.get("rate"); if (rate < 0d) rate = 0d; - changeRequested = true; + tempBasalRequested = true; } else { rate = -1; - changeRequested = false; + tempBasalRequested = false; } if (result.containsKey("duration")) { - duration = ((Double)result.get("duration")).intValue(); + duration = ((Double) result.get("duration")).intValue(); //changeRequested as above } else { duration = -1; - changeRequested = false; + tempBasalRequested = false; } } + bolusRequested = false; } public DetermineBasalResultAMA() { + hasPredictions = true; } @Override public DetermineBasalResultAMA clone() { DetermineBasalResultAMA newResult = new DetermineBasalResultAMA(); - newResult.reason = new String(reason); + newResult.reason = reason; newResult.rate = rate; newResult.duration = duration; - newResult.changeRequested = changeRequested; + newResult.tempBasalRequested = tempBasalRequested; newResult.rate = rate; newResult.duration = duration; - newResult.changeRequested = changeRequested; try { newResult.json = new JSONObject(json.toString()); @@ -90,72 +84,4 @@ public class DetermineBasalResultAMA extends APSResult { return null; } - public List getPredictions() { - List array = new ArrayList<>(); - try { - long startTime = date.getTime(); - if (json.has("predBGs")) { - JSONObject predBGs = json.getJSONObject("predBGs"); - if (predBGs.has("IOB")) { - JSONArray iob = predBGs.getJSONArray("IOB"); - for (int i = 1; i < iob.length(); i ++) { - BgReading bg = new BgReading(); - bg.value = iob.getInt(i); - bg.date = startTime + i * 5 * 60 * 1000L; - bg.isPrediction = true; - array.add(bg); - } - } - if (predBGs.has("aCOB")) { - JSONArray iob = predBGs.getJSONArray("aCOB"); - for (int i = 1; i < iob.length(); i ++) { - BgReading bg = new BgReading(); - bg.value = iob.getInt(i); - bg.date = startTime + i * 5 * 60 * 1000L; - bg.isPrediction = true; - array.add(bg); - } - } - if (predBGs.has("COB")) { - JSONArray iob = predBGs.getJSONArray("COB"); - for (int i = 1; i < iob.length(); i ++) { - BgReading bg = new BgReading(); - bg.value = iob.getInt(i); - bg.date = startTime + i * 5 * 60 * 1000L; - bg.isPrediction = true; - array.add(bg); - } - } - } - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - return array; - } - - public long getLatestPredictionsTime() { - long latest = 0; - try { - long startTime = date.getTime(); - if (json.has("predBGs")) { - JSONObject predBGs = json.getJSONObject("predBGs"); - if (predBGs.has("IOB")) { - JSONArray iob = predBGs.getJSONArray("IOB"); - latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); - } - if (predBGs.has("aCOB")) { - JSONArray iob = predBGs.getJSONArray("aCOB"); - latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); - } - if (predBGs.has("COB")) { - JSONArray iob = predBGs.getJSONArray("COB"); - latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); - } - } - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - - return latest; - } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAFragment.java index b35ebe25a6..2e6bb277e6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAFragment.java @@ -9,8 +9,6 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import com.squareup.otto.Subscribe; @@ -24,6 +22,7 @@ import info.nightscout.androidaps.R; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateGui; import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateResultGui; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.JSONFormatter; public class OpenAPSAMAFragment extends SubscriberFragment implements View.OnClickListener { @@ -63,7 +62,7 @@ public class OpenAPSAMAFragment extends SubscriberFragment implements View.OnCli updateGUI(); return view; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } return null; @@ -73,8 +72,8 @@ public class OpenAPSAMAFragment extends SubscriberFragment implements View.OnCli public void onClick(View view) { switch (view.getId()) { case R.id.openapsma_run: - OpenAPSAMAPlugin.getPlugin().invoke("OpenAPSAMA button"); - Answers.getInstance().logCustom(new CustomEvent("OpenAPS_AMA_Run")); + OpenAPSAMAPlugin.getPlugin().invoke("OpenAPSAMA button", false); + FabricPrivacy.getInstance().logCustom(new CustomEvent("OpenAPS_AMA_Run")); break; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java index 4c14f33533..17e1dd77e9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java @@ -8,7 +8,6 @@ import java.io.IOException; import java.util.Date; import info.nightscout.androidaps.Config; -import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.GlucoseStatus; @@ -16,8 +15,12 @@ import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.TempTarget; +import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.interfaces.APSInterface; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensResult; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; @@ -25,18 +28,16 @@ import info.nightscout.androidaps.plugins.Loop.APSResult; import info.nightscout.androidaps.plugins.Loop.ScriptReader; import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateGui; import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateResultGui; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.DateUtil; -import info.nightscout.utils.NSUpload; +import info.nightscout.utils.HardLimits; import info.nightscout.utils.Profiler; import info.nightscout.utils.Round; -import info.nightscout.utils.SP; -import info.nightscout.utils.SafeParse; -import info.nightscout.utils.ToastUtils; /** * Created by mike on 05.08.2016. */ -public class OpenAPSAMAPlugin implements PluginBase, APSInterface { +public class OpenAPSAMAPlugin extends PluginBase implements APSInterface { private static Logger log = LoggerFactory.getLogger(OpenAPSAMAPlugin.class); private static OpenAPSAMAPlugin openAPSAMAPlugin; @@ -54,75 +55,26 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { DetermineBasalResultAMA lastAPSResult = null; AutosensResult lastAutosensResult = null; - private boolean fragmentEnabled = false; - private boolean fragmentVisible = false; - - @Override - public String getName() { - return MainApp.instance().getString(R.string.openapsama); + public OpenAPSAMAPlugin() { + super(new PluginDescription() + .mainType(PluginType.APS) + .fragmentClass(OpenAPSAMAFragment.class.getName()) + .pluginName(R.string.openapsama) + .shortName(R.string.oaps_shortname) + .preferencesId(R.xml.pref_openapsama) + ); } @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.oaps_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); + public boolean specialEnableCondition() { + PumpInterface pump = ConfigBuilderPlugin.getActivePump(); + return pump == null || pump.getPumpDescription().isTempBasalCapable; } @Override - public boolean isEnabled(int type) { - boolean pumpCapable = ConfigBuilderPlugin.getActivePump() == null || ConfigBuilderPlugin.getActivePump().getPumpDescription().isTempBasalCapable; - return type == APS && fragmentEnabled && pumpCapable; - } - - @Override - public boolean isVisibleInTabs(int type) { - boolean pumpCapable = ConfigBuilderPlugin.getActivePump() == null || ConfigBuilderPlugin.getActivePump().getPumpDescription().isTempBasalCapable; - return type == APS && fragmentVisible && pumpCapable; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == APS) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return R.xml.pref_openapsama; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == APS) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public int getType() { - return PluginBase.APS; - } - - @Override - public String getFragmentClass() { - return OpenAPSAMAFragment.class.getName(); + public boolean specialShowInListCondition() { + PumpInterface pump = ConfigBuilderPlugin.getActivePump(); + return pump == null || pump.getPumpDescription().isTempBasalCapable; } @Override @@ -136,8 +88,8 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { } @Override - public void invoke(String initiator) { - log.debug("invoke from " + initiator); + public void invoke(String initiator, boolean tempBasalFallback) { + log.debug("invoke from " + initiator + " tempBasalFallback: " + tempBasalFallback); lastAPSResult = null; DetermineBasalAdapterAMAJS determineBasalAdapterAMAJS; try { @@ -149,6 +101,7 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { GlucoseStatus glucoseStatus = GlucoseStatus.getGlucoseStatusData(); Profile profile = MainApp.getConfigBuilder().getProfile(); + PumpInterface pump = ConfigBuilderPlugin.getActivePump(); if (profile == null) { MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.instance().getString(R.string.noprofileselected))); @@ -157,7 +110,7 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { return; } - if (!isEnabled(PluginBase.APS)) { + if (!isEnabled(PluginType.APS)) { MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.instance().getString(R.string.openapsma_disabled))); if (Config.logAPSResult) log.debug(MainApp.instance().getString(R.string.openapsma_disabled)); @@ -173,55 +126,53 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { String units = profile.getUnits(); - double maxIob = SP.getDouble("openapsma_max_iob", 1.5d); - double maxBasal = SP.getDouble("openapsma_max_basal", 1d); + double maxBasal = MainApp.getConstraintChecker().getMaxBasalAllowed(profile).value(); double minBg = Profile.toMgdl(profile.getTargetLow(), units); double maxBg = Profile.toMgdl(profile.getTargetHigh(), units); - double targetBg = (minBg + maxBg) / 2; + double targetBg = Profile.toMgdl(profile.getTarget(), units); minBg = Round.roundTo(minBg, 0.1d); maxBg = Round.roundTo(maxBg, 0.1d); Date start = new Date(); Date startPart = new Date(); - IobTotal[] iobArray = IobCobCalculatorPlugin.calculateIobArrayInDia(); + IobTotal[] iobArray = IobCobCalculatorPlugin.getPlugin().calculateIobArrayInDia(profile); Profiler.log(log, "calculateIobArrayInDia()", startPart); startPart = new Date(); - MealData mealData = MainApp.getConfigBuilder().getMealData(); + MealData mealData = TreatmentsPlugin.getPlugin().getMealData(); Profiler.log(log, "getMealData()", startPart); - maxIob = MainApp.getConfigBuilder().applyMaxIOBConstraints(maxIob); + double maxIob = MainApp.getConstraintChecker().getMaxIOBAllowed().value(); - minBg = verifyHardLimits(minBg, "minBg", Constants.VERY_HARD_LIMIT_MIN_BG[0], Constants.VERY_HARD_LIMIT_MIN_BG[1]); - maxBg = verifyHardLimits(maxBg, "maxBg", Constants.VERY_HARD_LIMIT_MAX_BG[0], Constants.VERY_HARD_LIMIT_MAX_BG[1]); - targetBg = verifyHardLimits(targetBg, "targetBg", Constants.VERY_HARD_LIMIT_TARGET_BG[0], Constants.VERY_HARD_LIMIT_TARGET_BG[1]); + minBg = HardLimits.verifyHardLimits(minBg, "minBg", HardLimits.VERY_HARD_LIMIT_MIN_BG[0], HardLimits.VERY_HARD_LIMIT_MIN_BG[1]); + maxBg = HardLimits.verifyHardLimits(maxBg, "maxBg", HardLimits.VERY_HARD_LIMIT_MAX_BG[0], HardLimits.VERY_HARD_LIMIT_MAX_BG[1]); + targetBg = HardLimits.verifyHardLimits(targetBg, "targetBg", HardLimits.VERY_HARD_LIMIT_TARGET_BG[0], HardLimits.VERY_HARD_LIMIT_TARGET_BG[1]); boolean isTempTarget = false; - TempTarget tempTarget = MainApp.getConfigBuilder().getTempTargetFromHistory(System.currentTimeMillis()); + TempTarget tempTarget = TreatmentsPlugin.getPlugin().getTempTargetFromHistory(System.currentTimeMillis()); if (tempTarget != null) { isTempTarget = true; - minBg = verifyHardLimits(tempTarget.low, "minBg", Constants.VERY_HARD_LIMIT_TEMP_MIN_BG[0], Constants.VERY_HARD_LIMIT_TEMP_MIN_BG[1]); - maxBg = verifyHardLimits(tempTarget.high, "maxBg", Constants.VERY_HARD_LIMIT_TEMP_MAX_BG[0], Constants.VERY_HARD_LIMIT_TEMP_MAX_BG[1]); - targetBg = verifyHardLimits((tempTarget.low + tempTarget.high) / 2, "targetBg", Constants.VERY_HARD_LIMIT_TEMP_TARGET_BG[0], Constants.VERY_HARD_LIMIT_TEMP_TARGET_BG[1]); + minBg = HardLimits.verifyHardLimits(tempTarget.low, "minBg", HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0], HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1]); + maxBg = HardLimits.verifyHardLimits(tempTarget.high, "maxBg", HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0], HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1]); + targetBg = HardLimits.verifyHardLimits(tempTarget.target(), "targetBg", HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0], HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1]); } - maxIob = verifyHardLimits(maxIob, "maxIob", 0, 7); - maxBasal = verifyHardLimits(maxBasal, "max_basal", 0.1, 10); - - if (!checkOnlyHardLimits(profile.getDia(), "dia", 2, 7)) return; - if (!checkOnlyHardLimits(profile.getIc(Profile.secondsFromMidnight()), "carbratio", 2, 100)) + if (!HardLimits.checkOnlyHardLimits(profile.getDia(), "dia", HardLimits.MINDIA, HardLimits.MAXDIA)) return; - if (!checkOnlyHardLimits(Profile.toMgdl(profile.getIsf(), units), "sens", 2, 900)) + if (!HardLimits.checkOnlyHardLimits(profile.getIcTimeFromMidnight(profile.secondsFromMidnight()), "carbratio", HardLimits.MINIC, HardLimits.MAXIC)) return; - if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.1, 10)) return; - if (!checkOnlyHardLimits(ConfigBuilderPlugin.getActivePump().getBaseBasalRate(), "current_basal", 0.01, 5)) + if (!HardLimits.checkOnlyHardLimits(Profile.toMgdl(profile.getIsf(), units), "sens", HardLimits.MINISF, HardLimits.MAXISF)) + return; + if (!HardLimits.checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.05, HardLimits.maxBasal())) + return; + if (!HardLimits.checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, HardLimits.maxBasal())) return; startPart = new Date(); - if (MainApp.getConfigBuilder().isAMAModeEnabled()) { - lastAutosensResult = IobCobCalculatorPlugin.detectSensitivityWithLock(IobCobCalculatorPlugin.oldestDataAvailable(), System.currentTimeMillis()); + if (MainApp.getConstraintChecker().isAutosensModeEnabled().value()) { + lastAutosensResult = IobCobCalculatorPlugin.getPlugin().detectSensitivityWithLock(IobCobCalculatorPlugin.getPlugin().oldestDataAvailable(), System.currentTimeMillis()); } else { lastAutosensResult = new AutosensResult(); } @@ -233,8 +184,7 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { try { determineBasalAdapterAMAJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, ConfigBuilderPlugin.getActivePump().getBaseBasalRate(), iobArray, glucoseStatus, mealData, lastAutosensResult.ratio, //autosensDataRatio - isTempTarget, - SafeParse.stringToDouble(SP.getString("openapsama_min_5m_carbimpact", "3.0"))//min_5m_carbimpact + isTempTarget ); } catch (JSONException e) { log.error("Unable to set data: " + e.toString()); @@ -244,16 +194,18 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { DetermineBasalResultAMA determineBasalResultAMA = determineBasalAdapterAMAJS.invoke(); Profiler.log(log, "AMA calculation", start); // Fix bug determine basal - if (determineBasalResultAMA.rate == 0d && determineBasalResultAMA.duration == 0 && !MainApp.getConfigBuilder().isTempBasalInProgress()) - determineBasalResultAMA.changeRequested = false; + if (determineBasalResultAMA.rate == 0d && determineBasalResultAMA.duration == 0 && !TreatmentsPlugin.getPlugin().isTempBasalInProgress()) + determineBasalResultAMA.tempBasalRequested = false; // limit requests on openloop mode - if (!MainApp.getConfigBuilder().isClosedModeEnabled()) { - if (MainApp.getConfigBuilder().isTempBasalInProgress() && determineBasalResultAMA.rate == 0 && determineBasalResultAMA.duration == 0) { + if (!MainApp.getConstraintChecker().isClosedLoopAllowed().value()) { + long now = System.currentTimeMillis(); + TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(now); + if (activeTemp != null && determineBasalResultAMA.rate == 0 && determineBasalResultAMA.duration == 0) { // going to cancel - } else if (MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultAMA.rate - MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()) < 0.1) { - determineBasalResultAMA.changeRequested = false; - } else if (!MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultAMA.rate - ConfigBuilderPlugin.getActivePump().getBaseBasalRate()) < 0.1) - determineBasalResultAMA.changeRequested = false; + } else if (activeTemp != null && Math.abs(determineBasalResultAMA.rate - activeTemp.tempBasalConvertedToAbsolute(now, profile)) < 0.1) { + determineBasalResultAMA.tempBasalRequested = false; + } else if (activeTemp == null && Math.abs(determineBasalResultAMA.rate - ConfigBuilderPlugin.getActivePump().getBaseBasalRate()) < 0.1) + determineBasalResultAMA.tempBasalRequested = false; } determineBasalResultAMA.iob = iobArray[0]; @@ -274,24 +226,4 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { //deviceStatus.suggested = determineBasalResultAMA.json; } - // safety checks - public static boolean checkOnlyHardLimits(Double value, String valueName, double lowLimit, double highLimit) { - return value.equals(verifyHardLimits(value, valueName, lowLimit, highLimit)); - } - - public static Double verifyHardLimits(Double value, String valueName, double lowLimit, double highLimit) { - Double newvalue = value; - if (newvalue < lowLimit || newvalue > highLimit) { - newvalue = Math.max(newvalue, lowLimit); - newvalue = Math.min(newvalue, highLimit); - String msg = String.format(MainApp.sResources.getString(R.string.openapsma_valueoutofrange), valueName); - msg += ".\n"; - msg += String.format(MainApp.sResources.getString(R.string.openapsma_valuelimitedto), value, newvalue); - log.error(msg); - NSUpload.uploadError(msg); - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), msg, R.raw.error); - } - return newvalue; - } - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalAdapterMAJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalAdapterMAJS.java index 723b224f90..6275154450 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalAdapterMAJS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalAdapterMAJS.java @@ -18,12 +18,14 @@ import java.lang.reflect.InvocationTargetException; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.plugins.Loop.ScriptReader; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.SP; public class DetermineBasalAdapterMAJS { @@ -171,7 +173,7 @@ public class DetermineBasalAdapterMAJS { mProfile.put("max_bg", maxBg); mProfile.put("target_bg", targetBg); mProfile.put("carb_ratio", profile.getIc()); - mProfile.put("sens", Profile.toMgdl(profile.getIsf().doubleValue(), units)); + mProfile.put("sens", Profile.toMgdl(profile.getIsf(), units)); mProfile.put("current_basal", basalRate); @@ -179,9 +181,12 @@ public class DetermineBasalAdapterMAJS { mProfile.put("out_units", "mmol/L"); } + long now = System.currentTimeMillis(); + TemporaryBasal tb = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(now); + mCurrentTemp = new JSONObject(); - mCurrentTemp.put("duration", MainApp.getConfigBuilder().getTempBasalRemainingMinutesFromHistory()); - mCurrentTemp.put("rate", MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()); + mCurrentTemp.put("duration", tb != null ? tb.getPlannedRemainingMinutes() : 0); + mCurrentTemp.put("rate", tb != null ? tb.tempBasalConvertedToAbsolute(now, profile) : 0d); mIobData = new JSONObject(); mIobData.put("iob", iobData.iob); //netIob @@ -193,7 +198,7 @@ public class DetermineBasalAdapterMAJS { mGlucoseStatus = new JSONObject(); mGlucoseStatus.put("glucose", glucoseStatus.glucose); - if (SP.getBoolean("always_use_shortavg", false)) { + if (SP.getBoolean(R.string.key_always_use_shortavg, false)) { mGlucoseStatus.put("delta", glucoseStatus.short_avgdelta); } else { mGlucoseStatus.put("delta", glucoseStatus.delta); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalResultMA.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalResultMA.java index e132c9f953..75928f09d0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalResultMA.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalResultMA.java @@ -6,7 +6,6 @@ import org.mozilla.javascript.NativeObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.plugins.Loop.APSResult; public class DetermineBasalResultMA extends APSResult { @@ -16,13 +15,12 @@ public class DetermineBasalResultMA extends APSResult { public double eventualBG; public double snoozeBG; public String mealAssist; - public IobTotal iob; public DetermineBasalResultMA(NativeObject result, JSONObject j) { json = j; if (result.containsKey("error")) { reason = (String) result.get("error"); - changeRequested = false; + tempBasalRequested = false; rate = -1; duration = -1; mealAssist = ""; @@ -33,17 +31,17 @@ public class DetermineBasalResultMA extends APSResult { if (result.containsKey("rate")) { rate = (Double) result.get("rate"); if (rate < 0d) rate = 0d; - changeRequested = true; + tempBasalRequested = true; } else { rate = -1; - changeRequested = false; + tempBasalRequested = false; } if (result.containsKey("duration")) { duration = ((Double) result.get("duration")).intValue(); //changeRequested as above } else { duration = -1; - changeRequested = false; + tempBasalRequested = false; } if (result.containsKey("mealAssist")) { mealAssist = result.get("mealAssist").toString(); @@ -60,10 +58,10 @@ public class DetermineBasalResultMA extends APSResult { newResult.reason = new String(reason); newResult.rate = rate; newResult.duration = duration; - newResult.changeRequested = changeRequested; + newResult.tempBasalRequested = isChangeRequested(); newResult.rate = rate; newResult.duration = duration; - newResult.changeRequested = changeRequested; + newResult.tempBasalRequested = isChangeRequested(); try { newResult.json = new JSONObject(json.toString()); @@ -72,7 +70,7 @@ public class DetermineBasalResultMA extends APSResult { } newResult.eventualBG = eventualBG; newResult.snoozeBG = snoozeBG; - newResult.mealAssist = new String(mealAssist); + newResult.mealAssist = mealAssist; return newResult; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAFragment.java index 1183a99e67..7360bb8b2a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAFragment.java @@ -9,8 +9,6 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import com.squareup.otto.Subscribe; @@ -22,6 +20,7 @@ import info.nightscout.androidaps.R; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateGui; import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateResultGui; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.JSONFormatter; public class OpenAPSMAFragment extends SubscriberFragment implements View.OnClickListener { @@ -57,7 +56,7 @@ public class OpenAPSMAFragment extends SubscriberFragment implements View.OnClic updateGUI(); return view; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } return null; @@ -67,8 +66,8 @@ public class OpenAPSMAFragment extends SubscriberFragment implements View.OnClic public void onClick(View view) { switch (view.getId()) { case R.id.openapsma_run: - OpenAPSMAPlugin.getPlugin().invoke("OpenAPSMA button"); - Answers.getInstance().logCustom(new CustomEvent("OpenAPS_MA_Run")); + OpenAPSMAPlugin.getPlugin().invoke("OpenAPSMA button", false); + FabricPrivacy.getInstance().logCustom(new CustomEvent("OpenAPS_MA_Run")); break; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java index d56dc44b89..00f1888851 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java @@ -8,7 +8,6 @@ import java.io.IOException; import java.util.Date; import info.nightscout.androidaps.Config; -import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.GlucoseStatus; @@ -16,26 +15,30 @@ import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.TempTarget; +import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.interfaces.APSInterface; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Loop.APSResult; import info.nightscout.androidaps.plugins.Loop.ScriptReader; import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateGui; import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateResultGui; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.DateUtil; +import info.nightscout.utils.HardLimits; import info.nightscout.utils.Profiler; import info.nightscout.utils.Round; -import info.nightscout.utils.SP; -import info.nightscout.utils.SafeParse; -import static info.nightscout.androidaps.plugins.OpenAPSAMA.OpenAPSAMAPlugin.checkOnlyHardLimits; -import static info.nightscout.androidaps.plugins.OpenAPSAMA.OpenAPSAMAPlugin.verifyHardLimits; +import static info.nightscout.utils.HardLimits.checkOnlyHardLimits; +import static info.nightscout.utils.HardLimits.verifyHardLimits; /** * Created by mike on 05.08.2016. */ -public class OpenAPSMAPlugin implements PluginBase, APSInterface { +public class OpenAPSMAPlugin extends PluginBase implements APSInterface { private static Logger log = LoggerFactory.getLogger(OpenAPSMAPlugin.class); private static OpenAPSMAPlugin openAPSMAPlugin; @@ -52,75 +55,26 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface { Date lastAPSRun = null; DetermineBasalResultMA lastAPSResult = null; - private boolean fragmentEnabled = false; - private boolean fragmentVisible = false; - - @Override - public String getName() { - return MainApp.instance().getString(R.string.openapsma); + public OpenAPSMAPlugin() { + super(new PluginDescription() + .mainType(PluginType.APS) + .fragmentClass(OpenAPSMAFragment.class.getName()) + .pluginName(R.string.openapsma) + .shortName(R.string.oaps_shortname) + .preferencesId(R.xml.pref_openapsma) + ); } @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.oaps_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); + public boolean specialEnableCondition() { + PumpInterface pump = ConfigBuilderPlugin.getActivePump(); + return pump == null || pump.getPumpDescription().isTempBasalCapable; } @Override - public boolean isEnabled(int type) { - boolean pumpCapable = ConfigBuilderPlugin.getActivePump() == null || ConfigBuilderPlugin.getActivePump().getPumpDescription().isTempBasalCapable; - return type == APS && fragmentEnabled && pumpCapable; - } - - @Override - public boolean isVisibleInTabs(int type) { - boolean pumpCapable = ConfigBuilderPlugin.getActivePump() == null || ConfigBuilderPlugin.getActivePump().getPumpDescription().isTempBasalCapable; - return type == APS && fragmentVisible && pumpCapable; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == APS) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return R.xml.pref_openapsma; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == APS) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public int getType() { - return PluginBase.APS; - } - - @Override - public String getFragmentClass() { - return OpenAPSMAFragment.class.getName(); + public boolean specialShowInListCondition() { + PumpInterface pump = ConfigBuilderPlugin.getActivePump(); + return pump == null || pump.getPumpDescription().isTempBasalCapable; } @Override @@ -134,8 +88,8 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface { } @Override - public void invoke(String initiator) { - log.debug("invoke from " + initiator); + public void invoke(String initiator, boolean tempBasalFallback) { + log.debug("invoke from " + initiator + " tempBasalFallback: " + tempBasalFallback); lastAPSResult = null; DetermineBasalAdapterMAJS determineBasalAdapterMAJS = null; try { @@ -147,6 +101,7 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface { GlucoseStatus glucoseStatus = GlucoseStatus.getGlucoseStatusData(); Profile profile = MainApp.getConfigBuilder().getProfile(); + PumpInterface pump = ConfigBuilderPlugin.getActivePump(); if (profile == null) { MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.instance().getString(R.string.noprofileselected))); @@ -155,7 +110,7 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface { return; } - if (!isEnabled(PluginBase.APS)) { + if (!isEnabled(PluginType.APS)) { MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.instance().getString(R.string.openapsma_disabled))); if (Config.logAPSResult) log.debug(MainApp.instance().getString(R.string.openapsma_disabled)); @@ -171,50 +126,48 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface { String units = profile.getUnits(); - Date now = new Date(); + double maxBasal = MainApp.getConstraintChecker().getMaxBasalAllowed(profile).value(); - double maxIob = SP.getDouble("openapsma_max_iob", 1.5d); - double maxBasal = SafeParse.stringToDouble(SP.getString("openapsma_max_basal", "1")); double minBg = Profile.toMgdl(profile.getTargetLow(), units); double maxBg = Profile.toMgdl(profile.getTargetHigh(), units); - double targetBg = (minBg + maxBg) / 2; + double targetBg = Profile.toMgdl(profile.getTarget(), units); minBg = Round.roundTo(minBg, 0.1d); maxBg = Round.roundTo(maxBg, 0.1d); Date start = new Date(); - MainApp.getConfigBuilder().updateTotalIOBTreatments(); - MainApp.getConfigBuilder().updateTotalIOBTempBasals(); - IobTotal bolusIob = MainApp.getConfigBuilder().getLastCalculationTreatments(); - IobTotal basalIob = MainApp.getConfigBuilder().getLastCalculationTempBasals(); + TreatmentsPlugin.getPlugin().updateTotalIOBTreatments(); + TreatmentsPlugin.getPlugin().updateTotalIOBTempBasals(); + IobTotal bolusIob = TreatmentsPlugin.getPlugin().getLastCalculationTreatments(); + IobTotal basalIob = TreatmentsPlugin.getPlugin().getLastCalculationTempBasals(); IobTotal iobTotal = IobTotal.combine(bolusIob, basalIob).round(); - MealData mealData = MainApp.getConfigBuilder().getMealData(); + MealData mealData = TreatmentsPlugin.getPlugin().getMealData(); - maxIob = MainApp.getConfigBuilder().applyMaxIOBConstraints(maxIob); + double maxIob = MainApp.getConstraintChecker().getMaxIOBAllowed().value(); Profiler.log(log, "MA data gathering", start); - minBg = verifyHardLimits(minBg, "minBg", Constants.VERY_HARD_LIMIT_MIN_BG[0], Constants.VERY_HARD_LIMIT_MIN_BG[1]); - maxBg = verifyHardLimits(maxBg, "maxBg", Constants.VERY_HARD_LIMIT_MAX_BG[0], Constants.VERY_HARD_LIMIT_MAX_BG[1]); - targetBg = verifyHardLimits(targetBg, "targetBg", Constants.VERY_HARD_LIMIT_TARGET_BG[0], Constants.VERY_HARD_LIMIT_TARGET_BG[1]); + minBg = verifyHardLimits(minBg, "minBg", HardLimits.VERY_HARD_LIMIT_MIN_BG[0], HardLimits.VERY_HARD_LIMIT_MIN_BG[1]); + maxBg = verifyHardLimits(maxBg, "maxBg", HardLimits.VERY_HARD_LIMIT_MAX_BG[0], HardLimits.VERY_HARD_LIMIT_MAX_BG[1]); + targetBg = verifyHardLimits(targetBg, "targetBg", HardLimits.VERY_HARD_LIMIT_TARGET_BG[0], HardLimits.VERY_HARD_LIMIT_TARGET_BG[1]); - TempTarget tempTarget = MainApp.getConfigBuilder().getTempTargetFromHistory(System.currentTimeMillis()); + TempTarget tempTarget = TreatmentsPlugin.getPlugin().getTempTargetFromHistory(System.currentTimeMillis()); if (tempTarget != null) { - minBg = verifyHardLimits(tempTarget.low, "minBg", Constants.VERY_HARD_LIMIT_TEMP_MIN_BG[0], Constants.VERY_HARD_LIMIT_TEMP_MIN_BG[1]); - maxBg = verifyHardLimits(tempTarget.high, "maxBg", Constants.VERY_HARD_LIMIT_TEMP_MAX_BG[0], Constants.VERY_HARD_LIMIT_TEMP_MAX_BG[1]); - targetBg = verifyHardLimits((tempTarget.low + tempTarget.high) / 2, "targetBg", Constants.VERY_HARD_LIMIT_TEMP_TARGET_BG[0], Constants.VERY_HARD_LIMIT_TEMP_TARGET_BG[1]); + minBg = verifyHardLimits(tempTarget.low, "minBg", HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0], HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1]); + maxBg = verifyHardLimits(tempTarget.high, "maxBg", HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0], HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1]); + targetBg = verifyHardLimits(tempTarget.target(), "targetBg", HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0], HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1]); } - maxIob = verifyHardLimits(maxIob, "maxIob", 0, 7); - maxBasal = verifyHardLimits(maxBasal, "max_basal", 0.1, 10); - - if (!checkOnlyHardLimits(profile.getDia(), "dia", 2, 7)) return; - if (!checkOnlyHardLimits(profile.getIc(), "carbratio", 2, 100)) return; - if (!checkOnlyHardLimits(Profile.toMgdl(profile.getIsf(), units), "sens", 2, 900)) + if (!checkOnlyHardLimits(profile.getDia(), "dia", HardLimits.MINDIA, HardLimits.MAXDIA)) return; - if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.1, 10)) return; - if (!checkOnlyHardLimits(ConfigBuilderPlugin.getActivePump().getBaseBasalRate(), "current_basal", 0.01, 5)) + if (!checkOnlyHardLimits(profile.getIcTimeFromMidnight(profile.secondsFromMidnight()), "carbratio", HardLimits.MINIC, HardLimits.MAXIC)) + return; + if (!checkOnlyHardLimits(Profile.toMgdl(profile.getIsf(), units), "sens", HardLimits.MINISF, HardLimits.MAXISF)) + return; + if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.05, HardLimits.maxBasal())) + return; + if (!checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, HardLimits.maxBasal())) return; start = new Date(); @@ -226,18 +179,21 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface { Profiler.log(log, "MA calculation", start); + long now = System.currentTimeMillis(); + DetermineBasalResultMA determineBasalResultMA = determineBasalAdapterMAJS.invoke(); // Fix bug determinef basal - if (determineBasalResultMA.rate == 0d && determineBasalResultMA.duration == 0 && !MainApp.getConfigBuilder().isTempBasalInProgress()) - determineBasalResultMA.changeRequested = false; + if (determineBasalResultMA.rate == 0d && determineBasalResultMA.duration == 0 && !TreatmentsPlugin.getPlugin().isTempBasalInProgress()) + determineBasalResultMA.tempBasalRequested = false; // limit requests on openloop mode - if (!MainApp.getConfigBuilder().isClosedModeEnabled()) { - if (MainApp.getConfigBuilder().isTempBasalInProgress() && determineBasalResultMA.rate == 0 && determineBasalResultMA.duration == 0) { + if (!MainApp.getConstraintChecker().isClosedLoopAllowed().value()) { + TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(now); + if (activeTemp != null && determineBasalResultMA.rate == 0 && determineBasalResultMA.duration == 0) { // going to cancel - } else if (MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultMA.rate - MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()) < 0.1) { - determineBasalResultMA.changeRequested = false; - } else if (!MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultMA.rate - ConfigBuilderPlugin.getActivePump().getBaseBasalRate()) < 0.1) - determineBasalResultMA.changeRequested = false; + } else if (activeTemp != null && Math.abs(determineBasalResultMA.rate - activeTemp.tempBasalConvertedToAbsolute(now, profile)) < 0.1) { + determineBasalResultMA.tempBasalRequested = false; + } else if (activeTemp == null && Math.abs(determineBasalResultMA.rate - ConfigBuilderPlugin.getActivePump().getBaseBasalRate()) < 0.1) + determineBasalResultMA.tempBasalRequested = false; } determineBasalResultMA.iob = iobTotal; @@ -250,7 +206,7 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface { lastDetermineBasalAdapterMAJS = determineBasalAdapterMAJS; lastAPSResult = determineBasalResultMA; - lastAPSRun = now; + lastAPSRun = new Date(now); MainApp.bus().post(new EventOpenAPSUpdateGui()); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/DetermineBasalAdapterSMBJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/DetermineBasalAdapterSMBJS.java new file mode 100644 index 0000000000..da96241e61 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/DetermineBasalAdapterSMBJS.java @@ -0,0 +1,355 @@ +package info.nightscout.androidaps.plugins.OpenAPSSMB; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.mozilla.javascript.Callable; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.NativeJSON; +import org.mozilla.javascript.NativeObject; +import org.mozilla.javascript.RhinoException; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; +import org.mozilla.javascript.Undefined; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; + +import info.nightscout.androidaps.Config; +import info.nightscout.androidaps.Constants; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.data.IobTotal; +import info.nightscout.androidaps.data.MealData; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; +import info.nightscout.androidaps.plugins.Loop.ScriptReader; +import info.nightscout.androidaps.plugins.OpenAPSMA.LoggerCallback; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; +import info.nightscout.utils.SP; +import info.nightscout.utils.SafeParse; + +public class DetermineBasalAdapterSMBJS { + private static Logger log = LoggerFactory.getLogger(DetermineBasalAdapterSMBJS.class); + + + private ScriptReader mScriptReader = null; + private JSONObject mProfile; + private JSONObject mGlucoseStatus; + private JSONArray mIobData; + private JSONObject mMealData; + private JSONObject mCurrentTemp; + private JSONObject mAutosensData = null; + private boolean mMicrobolusAllowed; + private boolean mSMBAlwaysAllowed; + + private String storedCurrentTemp = null; + private String storedIobData = null; + + private String storedGlucoseStatus = null; + private String storedProfile = null; + private String storedMeal_data = null; + private String storedAutosens_data = null; + private String storedMicroBolusAllowed = null; + private String storedSMBAlwaysAllowed = null; + + private String scriptDebug = ""; + + /** + * Main code + */ + + public DetermineBasalAdapterSMBJS(ScriptReader scriptReader) throws IOException { + mScriptReader = scriptReader; + } + + + public DetermineBasalResultSMB invoke() { + + + log.debug(">>> Invoking detemine_basal <<<"); + log.debug("Glucose status: " + (storedGlucoseStatus = mGlucoseStatus.toString())); + log.debug("IOB data: " + (storedIobData = mIobData.toString())); + log.debug("Current temp: " + (storedCurrentTemp = mCurrentTemp.toString())); + log.debug("Profile: " + (storedProfile = mProfile.toString())); + log.debug("Meal data: " + (storedMeal_data = mMealData.toString())); + if (mAutosensData != null) + log.debug("Autosens data: " + (storedAutosens_data = mAutosensData.toString())); + else + log.debug("Autosens data: " + (storedAutosens_data = "undefined")); + log.debug("Reservoir data: " + "undefined"); + log.debug("MicroBolusAllowed: " + (storedMicroBolusAllowed = "" + mMicrobolusAllowed)); + log.debug("SMBAlwaysAllowed: " + (storedSMBAlwaysAllowed = "" + mSMBAlwaysAllowed)); + + DetermineBasalResultSMB determineBasalResultSMB = null; + + Context rhino = Context.enter(); + Scriptable scope = rhino.initStandardObjects(); + // Turn off optimization to make Rhino Android compatible + rhino.setOptimizationLevel(-1); + + try { + + //register logger callback for console.log and console.error + ScriptableObject.defineClass(scope, LoggerCallback.class); + Scriptable myLogger = rhino.newObject(scope, "LoggerCallback", null); + scope.put("console2", scope, myLogger); + rhino.evaluateString(scope, readFile("OpenAPSAMA/loggerhelper.js"), "JavaScript", 0, null); + + //set module parent + rhino.evaluateString(scope, "var module = {\"parent\":Boolean(1)};", "JavaScript", 0, null); + rhino.evaluateString(scope, "var round_basal = function round_basal(basal, profile) { return basal; };", "JavaScript", 0, null); + rhino.evaluateString(scope, "require = function() {return round_basal;};", "JavaScript", 0, null); + + //generate functions "determine_basal" and "setTempBasal" + rhino.evaluateString(scope, readFile("OpenAPSSMB/determine-basal.js"), "JavaScript", 0, null); + rhino.evaluateString(scope, readFile("OpenAPSSMB/basal-set-temp.js"), "setTempBasal.js", 0, null); + Object determineBasalObj = scope.get("determine_basal", scope); + Object setTempBasalFunctionsObj = scope.get("tempBasalFunctions", scope); + + //call determine-basal + if (determineBasalObj instanceof Function && setTempBasalFunctionsObj instanceof NativeObject) { + Function determineBasalJS = (Function) determineBasalObj; + + //prepare parameters + Object[] params = new Object[]{ + makeParam(mGlucoseStatus, rhino, scope), + makeParam(mCurrentTemp, rhino, scope), + makeParamArray(mIobData, rhino, scope), + makeParam(mProfile, rhino, scope), + makeParam(mAutosensData, rhino, scope), + makeParam(mMealData, rhino, scope), + setTempBasalFunctionsObj, + new Boolean(mMicrobolusAllowed), + makeParam(null, rhino, scope) // reservoir data as undefined + }; + + + NativeObject jsResult = (NativeObject) determineBasalJS.call(rhino, scope, scope, params); + scriptDebug = LoggerCallback.getScriptDebug(); + + // Parse the jsResult object to a JSON-String + String result = NativeJSON.stringify(rhino, scope, jsResult, null, null).toString(); + if (Config.logAPSResult) + log.debug("Result: " + result); + try { + determineBasalResultSMB = new DetermineBasalResultSMB(new JSONObject(result)); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + } else { + log.debug("Problem loading JS Functions"); + } + } catch (IOException e) { + log.debug("IOException"); + } catch (RhinoException e) { + log.error("RhinoException: (" + e.lineNumber() + "," + e.columnNumber() + ") " + e.toString()); + } catch (IllegalAccessException e) { + log.error(e.toString()); + } catch (InstantiationException e) { + log.error(e.toString()); + } catch (InvocationTargetException e) { + log.error(e.toString()); + } finally { + Context.exit(); + } + + storedGlucoseStatus = mGlucoseStatus.toString(); + storedIobData = mIobData.toString(); + storedCurrentTemp = mCurrentTemp.toString(); + storedProfile = mProfile.toString(); + storedMeal_data = mMealData.toString(); + + return determineBasalResultSMB; + + } + + String getGlucoseStatusParam() { + return storedGlucoseStatus; + } + + String getCurrentTempParam() { + return storedCurrentTemp; + } + + String getIobDataParam() { + return storedIobData; + } + + String getProfileParam() { + return storedProfile; + } + + String getMealDataParam() { + return storedMeal_data; + } + + String getAutosensDataParam() { + return storedAutosens_data; + } + + String getMicroBolusAllowedParam() { + return storedMicroBolusAllowed; + } + + String getScriptDebug() { + return scriptDebug; + } + + public void setData(Profile profile, + double maxIob, + double maxBasal, + double minBg, + double maxBg, + double targetBg, + double basalrate, + IobTotal[] iobArray, + GlucoseStatus glucoseStatus, + MealData mealData, + double autosensDataRatio, + boolean tempTargetSet, + boolean microBolusAllowed, + boolean advancedFiltering + ) throws JSONException { + + String units = profile.getUnits(); + + mProfile = new JSONObject(); + + mProfile.put("max_iob", maxIob); + //mProfile.put("dia", profile.getDia()); + mProfile.put("type", "current"); + mProfile.put("max_daily_basal", profile.getMaxDailyBasal()); + mProfile.put("max_basal", maxBasal); + mProfile.put("min_bg", minBg); + mProfile.put("max_bg", maxBg); + mProfile.put("target_bg", targetBg); + mProfile.put("carb_ratio", profile.getIc()); + mProfile.put("sens", Profile.toMgdl(profile.getIsf(), units)); + mProfile.put("max_daily_safety_multiplier", SP.getInt(R.string.key_openapsama_max_daily_safety_multiplier, 3)); + mProfile.put("current_basal_safety_multiplier", SP.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4d)); + + mProfile.put("high_temptarget_raises_sensitivity", SMBDefaults.high_temptarget_raises_sensitivity); + mProfile.put("low_temptarget_lowers_sensitivity", SMBDefaults.low_temptarget_lowers_sensitivity); + mProfile.put("sensitivity_raises_target", SMBDefaults.sensitivity_raises_target); + mProfile.put("resistance_lowers_target", SMBDefaults.resistance_lowers_target); + mProfile.put("adv_target_adjustments", SMBDefaults.adv_target_adjustments); + mProfile.put("exercise_mode", SMBDefaults.exercise_mode); + mProfile.put("half_basal_exercise_target", SMBDefaults.half_basal_exercise_target); + mProfile.put("maxCOB", SMBDefaults.maxCOB); + mProfile.put("skip_neutral_temps", SMBDefaults.skip_neutral_temps); + //align with max-absorption model in AMA sensitivity + if(mealData.usedMinCarbsImpact > 0){ + mProfile.put("min_5m_carbimpact", mealData.usedMinCarbsImpact); + } else { + mProfile.put("min_5m_carbimpact", SP.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact)); + } + mProfile.put("remainingCarbsCap", SMBDefaults.remainingCarbsCap); + mProfile.put("enableUAM", SP.getBoolean(R.string.key_use_uam, false)&& advancedFiltering); + mProfile.put("A52_risk_enable", SMBDefaults.A52_risk_enable); + mProfile.put("enableSMB_with_COB", SP.getBoolean(R.string.key_enableSMB_with_COB, false)); + mProfile.put("enableSMB_with_temptarget", SP.getBoolean(R.string.key_enableSMB_with_temptarget, false)); + mProfile.put("allowSMB_with_high_temptarget", SP.getBoolean(R.string.key_allowSMB_with_high_temptarget, false)); + mProfile.put("enableSMB_always", SP.getBoolean(R.string.key_enableSMB_always, false) && advancedFiltering); + mProfile.put("enableSMB_after_carbs", SP.getBoolean(R.string.key_enableSMB_after_carbs, false) && advancedFiltering); + mProfile.put("maxSMBBasalMinutes", SP.getInt("key_smbmaxminutes", SMBDefaults.maxSMBBasalMinutes)); + mProfile.put("carbsReqThreshold", SMBDefaults.carbsReqThreshold); + + mProfile.put("current_basal", basalrate); + mProfile.put("temptargetSet", tempTargetSet); + mProfile.put("autosens_max", SafeParse.stringToDouble(SP.getString(R.string.key_openapsama_autosens_max, "1.2"))); + + if (units.equals(Constants.MMOL)) { + mProfile.put("out_units", "mmol/L"); + } + + + long now = System.currentTimeMillis(); + TemporaryBasal tb = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(now); + + mCurrentTemp = new JSONObject(); + mCurrentTemp.put("temp", "absolute"); + mCurrentTemp.put("duration", tb != null ? tb.getPlannedRemainingMinutes() : 0); + mCurrentTemp.put("rate", tb != null ? tb.tempBasalConvertedToAbsolute(now, profile) : 0d); + + // as we have non default temps longer than 30 mintues + TemporaryBasal tempBasal = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); + if (tempBasal != null) { + mCurrentTemp.put("minutesrunning", tempBasal.getRealDuration()); + } + + mIobData = IobCobCalculatorPlugin.convertToJSONArray(iobArray); + + mGlucoseStatus = new JSONObject(); + mGlucoseStatus.put("glucose", glucoseStatus.glucose); + + if (SP.getBoolean(R.string.key_always_use_shortavg, false)) { + mGlucoseStatus.put("delta", glucoseStatus.short_avgdelta); + } else { + mGlucoseStatus.put("delta", glucoseStatus.delta); + } + mGlucoseStatus.put("short_avgdelta", glucoseStatus.short_avgdelta); + mGlucoseStatus.put("long_avgdelta", glucoseStatus.long_avgdelta); + mGlucoseStatus.put("date", glucoseStatus.date); + + mMealData = new JSONObject(); + mMealData.put("carbs", mealData.carbs); + mMealData.put("boluses", mealData.boluses); + mMealData.put("mealCOB", mealData.mealCOB); + mMealData.put("slopeFromMaxDeviation", mealData.slopeFromMaxDeviation); + mMealData.put("slopeFromMinDeviation", mealData.slopeFromMinDeviation); + mMealData.put("lastBolusTime", mealData.lastBolusTime); + mMealData.put("lastCarbTime", mealData.lastCarbTime); + + + if (MainApp.getConstraintChecker().isAutosensModeEnabled().value()) { + mAutosensData = new JSONObject(); + mAutosensData.put("ratio", autosensDataRatio); + } else { + mAutosensData = new JSONObject(); + mAutosensData.put("ratio", 1.0); + } + mMicrobolusAllowed = microBolusAllowed; + mSMBAlwaysAllowed = advancedFiltering; + + } + + public Object makeParam(JSONObject jsonObject, Context rhino, Scriptable scope) { + + if (jsonObject == null) return Undefined.instance; + + Object param = NativeJSON.parse(rhino, scope, jsonObject.toString(), new Callable() { + @Override + public Object call(Context context, Scriptable scriptable, Scriptable scriptable1, Object[] objects) { + return objects[1]; + } + }); + return param; + } + + public Object makeParamArray(JSONArray jsonArray, Context rhino, Scriptable scope) { + //Object param = NativeJSON.parse(rhino, scope, "{myarray: " + jsonArray.toString() + " }", new Callable() { + Object param = NativeJSON.parse(rhino, scope, jsonArray.toString(), new Callable() { + @Override + public Object call(Context context, Scriptable scriptable, Scriptable scriptable1, Object[] objects) { + return objects[1]; + } + }); + return param; + } + + public String readFile(String filename) throws IOException { + byte[] bytes = mScriptReader.readFile(filename); + String string = new String(bytes, "UTF-8"); + if (string.startsWith("#!/usr/bin/env node")) { + string = string.substring(20); + } + return string; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/DetermineBasalResultSMB.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/DetermineBasalResultSMB.java new file mode 100644 index 0000000000..59c1dd95e3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/DetermineBasalResultSMB.java @@ -0,0 +1,104 @@ +package info.nightscout.androidaps.plugins.OpenAPSSMB; + +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; + +import info.nightscout.androidaps.plugins.Loop.APSResult; +import info.nightscout.utils.DateUtil; + +public class DetermineBasalResultSMB extends APSResult { + private static final Logger log = LoggerFactory.getLogger(DetermineBasalResultSMB.class); + + public double eventualBG; + public double snoozeBG; + public double insulinReq; + public double carbsReq; + + public DetermineBasalResultSMB(JSONObject result) { + this(); + date = new Date(); + json = result; + try { + if (result.has("error")) { + reason = result.getString("error"); + return; + } + + reason = result.getString("reason"); + if (result.has("eventualBG")) eventualBG = result.getDouble("eventualBG"); + if (result.has("snoozeBG")) snoozeBG = result.getDouble("snoozeBG"); + if (result.has("insulinReq")) insulinReq = result.getDouble("insulinReq"); + if (result.has("carbsReq")) carbsReq = result.getDouble("carbsReq"); + + if (result.has("rate") && result.has("duration")) { + tempBasalRequested = true; + rate = result.getDouble("rate"); + if (rate < 0d) rate = 0d; + duration = result.getInt("duration"); + } else { + rate = -1; + duration = -1; + } + + if (result.has("units")) { + bolusRequested = true; + smb = result.getDouble("units"); + } else { + smb = 0d; + } + + if (result.has("deliverAt")) { + String date = result.getString("deliverAt"); + try { + deliverAt = DateUtil.fromISODateString(date).getTime(); + } catch (Exception e) { + log.warn("Error parsing 'deliverAt' date: " + date, e); + } + } + } catch (JSONException e) { + log.error("Error parsing determine-basal result JSON", e); + } + } + + public DetermineBasalResultSMB() { + hasPredictions = true; + } + + @Override + public DetermineBasalResultSMB clone() { + DetermineBasalResultSMB newResult = new DetermineBasalResultSMB(); + newResult.reason = reason; + newResult.rate = rate; + newResult.duration = duration; + newResult.tempBasalRequested = tempBasalRequested; + newResult.bolusRequested = bolusRequested; + newResult.rate = rate; + newResult.duration = duration; + newResult.smb = smb; + newResult.deliverAt = deliverAt; + + try { + newResult.json = new JSONObject(json.toString()); + } catch (JSONException e) { + log.error("Error clone parsing determine-basal result", e); + } + newResult.eventualBG = eventualBG; + newResult.snoozeBG = snoozeBG; + newResult.date = date; + return newResult; + } + + @Override + public JSONObject json() { + try { + return new JSONObject(this.json.toString()); + } catch (JSONException e) { + log.error("Error converting determine-basal result to JSON", e); + } + return null; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBFragment.java new file mode 100644 index 0000000000..7780fcc9b4 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBFragment.java @@ -0,0 +1,142 @@ +package info.nightscout.androidaps.plugins.OpenAPSSMB; + +import android.app.Activity; +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 com.crashlytics.android.answers.CustomEvent; +import com.squareup.otto.Subscribe; + +import org.json.JSONArray; +import org.json.JSONException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.Common.SubscriberFragment; +import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateGui; +import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateResultGui; +import info.nightscout.utils.FabricPrivacy; +import info.nightscout.utils.JSONFormatter; + +public class OpenAPSSMBFragment extends SubscriberFragment { + private static Logger log = LoggerFactory.getLogger(OpenAPSSMBFragment.class); + + @BindView(R.id.openapsma_run) + Button run; + @BindView(R.id.openapsma_lastrun) + TextView lastRunView; + @BindView(R.id.openapsma_constraints) + TextView constraintsView; + @BindView(R.id.openapsma_glucosestatus) + TextView glucoseStatusView; + @BindView(R.id.openapsma_currenttemp) + TextView currentTempView; + @BindView(R.id.openapsma_iobdata) + TextView iobDataView; + @BindView(R.id.openapsma_profile) + TextView profileView; + @BindView(R.id.openapsma_mealdata) + TextView mealDataView; + @BindView(R.id.openapsma_autosensdata) + TextView autosensDataView; + @BindView(R.id.openapsma_result) + TextView resultView; + @BindView(R.id.openapsma_scriptdebugdata) + TextView scriptdebugView; + @BindView(R.id.openapsma_request) + TextView requestView; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.openapsama_fragment, container, false); + + unbinder = ButterKnife.bind(this, view); + return view; + } + + @OnClick(R.id.openapsma_run) + public void onRunClick() { + OpenAPSSMBPlugin.getPlugin().invoke("OpenAPSSMB button", false); + FabricPrivacy.getInstance().logCustom(new CustomEvent("OpenAPS_SMB_Run")); + } + + @Subscribe + public void onStatusEvent(final EventOpenAPSUpdateGui ev) { + updateGUI(); + } + + @Subscribe + public void onStatusEvent(final EventOpenAPSUpdateResultGui ev) { + updateResultGUI(ev.text); + } + + @Override + protected void updateGUI() { + Activity activity = getActivity(); + if (activity != null) + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + OpenAPSSMBPlugin plugin = OpenAPSSMBPlugin.getPlugin(); + DetermineBasalResultSMB lastAPSResult = plugin.lastAPSResult; + if (lastAPSResult != null) { + resultView.setText(JSONFormatter.format(lastAPSResult.json)); + requestView.setText(lastAPSResult.toSpanned()); + } + DetermineBasalAdapterSMBJS determineBasalAdapterSMBJS = plugin.lastDetermineBasalAdapterSMBJS; + if (determineBasalAdapterSMBJS != null) { + glucoseStatusView.setText(JSONFormatter.format(determineBasalAdapterSMBJS.getGlucoseStatusParam()).toString().trim()); + currentTempView.setText(JSONFormatter.format(determineBasalAdapterSMBJS.getCurrentTempParam()).toString().trim()); + try { + JSONArray iobArray = new JSONArray(determineBasalAdapterSMBJS.getIobDataParam()); + iobDataView.setText((String.format(MainApp.sResources.getString(R.string.array_of_elements), iobArray.length()) + "\n" + JSONFormatter.format(iobArray.getString(0))).trim()); + } catch (JSONException e) { + log.error("Unhandled exception", e); + iobDataView.setText("JSONException see log for details"); + } + profileView.setText(JSONFormatter.format(determineBasalAdapterSMBJS.getProfileParam()).toString().trim()); + mealDataView.setText(JSONFormatter.format(determineBasalAdapterSMBJS.getMealDataParam()).toString().trim()); + scriptdebugView.setText(determineBasalAdapterSMBJS.getScriptDebug().trim()); + if (lastAPSResult != null && lastAPSResult.inputConstraints != null) + constraintsView.setText(lastAPSResult.inputConstraints.getReasons().trim()); + } + if (plugin.lastAPSRun != null) { + lastRunView.setText(plugin.lastAPSRun.toLocaleString().trim()); + } + if (plugin.lastAutosensResult != null) { + autosensDataView.setText(JSONFormatter.format(plugin.lastAutosensResult.json()).toString().trim()); + } + } + }); + } + + void updateResultGUI(final String text) { + Activity activity = getActivity(); + if (activity != null) + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + resultView.setText(text); + glucoseStatusView.setText(""); + currentTempView.setText(""); + iobDataView.setText(""); + profileView.setText(""); + mealDataView.setText(""); + autosensDataView.setText(""); + scriptdebugView.setText(""); + requestView.setText(""); + lastRunView.setText(""); + } + }); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBPlugin.java new file mode 100644 index 0000000000..cd7872ecb1 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBPlugin.java @@ -0,0 +1,268 @@ +package info.nightscout.androidaps.plugins.OpenAPSSMB; + +import org.json.JSONException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Date; + +import info.nightscout.androidaps.Config; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.data.IobTotal; +import info.nightscout.androidaps.data.MealData; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.db.TempTarget; +import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.interfaces.APSInterface; +import info.nightscout.androidaps.interfaces.Constraint; +import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensResult; +import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; +import info.nightscout.androidaps.plugins.Loop.APSResult; +import info.nightscout.androidaps.plugins.Loop.ScriptReader; +import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateGui; +import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateResultGui; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.HardLimits; +import info.nightscout.utils.NSUpload; +import info.nightscout.utils.Profiler; +import info.nightscout.utils.Round; +import info.nightscout.utils.ToastUtils; + +/** + * Created by mike on 05.08.2016. + */ +public class OpenAPSSMBPlugin extends PluginBase implements APSInterface { + private static Logger log = LoggerFactory.getLogger(OpenAPSSMBPlugin.class); + + private static OpenAPSSMBPlugin openAPSSMBPlugin; + + public static OpenAPSSMBPlugin getPlugin() { + if (openAPSSMBPlugin == null) { + openAPSSMBPlugin = new OpenAPSSMBPlugin(); + } + return openAPSSMBPlugin; + } + + // last values + DetermineBasalAdapterSMBJS lastDetermineBasalAdapterSMBJS = null; + Date lastAPSRun = null; + DetermineBasalResultSMB lastAPSResult = null; + AutosensResult lastAutosensResult = null; + + private OpenAPSSMBPlugin() { + super(new PluginDescription() + .mainType(PluginType.APS) + .fragmentClass(OpenAPSSMBFragment.class.getName()) + .pluginName(R.string.openapssmb) + .shortName(R.string.smb_shortname) + .preferencesId(R.xml.pref_openapssmb) + ); + } + + @Override + public boolean specialEnableCondition() { + PumpInterface pump = ConfigBuilderPlugin.getActivePump(); + return pump == null || pump.getPumpDescription().isTempBasalCapable; + } + + @Override + public boolean specialShowInListCondition() { + PumpInterface pump = ConfigBuilderPlugin.getActivePump(); + return pump == null || pump.getPumpDescription().isTempBasalCapable; + } + + @Override + public APSResult getLastAPSResult() { + return lastAPSResult; + } + + @Override + public Date getLastAPSRun() { + return lastAPSRun; + } + + @Override + public void invoke(String initiator, boolean tempBasalFallback) { + log.debug("invoke from " + initiator + " tempBasalFallback: " + tempBasalFallback); + lastAPSResult = null; + DetermineBasalAdapterSMBJS determineBasalAdapterSMBJS = null; + try { + determineBasalAdapterSMBJS = new DetermineBasalAdapterSMBJS(new ScriptReader(MainApp.instance().getBaseContext())); + } catch (IOException e) { + log.error(e.getMessage(), e); + return; + } + + GlucoseStatus glucoseStatus = GlucoseStatus.getGlucoseStatusData(); + Profile profile = MainApp.getConfigBuilder().getProfile(); + PumpInterface pump = ConfigBuilderPlugin.getActivePump(); + + if (profile == null) { + MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.instance().getString(R.string.noprofileselected))); + if (Config.logAPSResult) + log.debug(MainApp.instance().getString(R.string.noprofileselected)); + return; + } + + if (!isEnabled(PluginType.APS)) { + MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.instance().getString(R.string.openapsma_disabled))); + if (Config.logAPSResult) + log.debug(MainApp.instance().getString(R.string.openapsma_disabled)); + return; + } + + if (glucoseStatus == null) { + MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.instance().getString(R.string.openapsma_noglucosedata))); + if (Config.logAPSResult) + log.debug(MainApp.instance().getString(R.string.openapsma_noglucosedata)); + return; + } + + String units = profile.getUnits(); + + Constraint inputConstraints = new Constraint<>(0d); // fake. only for collecting all results + + Constraint maxBasalConstraint = MainApp.getConstraintChecker().getMaxBasalAllowed(profile); + inputConstraints.copyReasons(maxBasalConstraint); + double maxBasal = maxBasalConstraint.value(); + double minBg = Profile.toMgdl(profile.getTargetLow(), units); + double maxBg = Profile.toMgdl(profile.getTargetHigh(), units); + double targetBg = Profile.toMgdl(profile.getTarget(), units); + + minBg = Round.roundTo(minBg, 0.1d); + maxBg = Round.roundTo(maxBg, 0.1d); + + Date start = new Date(); + Date startPart = new Date(); + IobTotal[] iobArray = IobCobCalculatorPlugin.getPlugin().calculateIobArrayForSMB(profile); + Profiler.log(log, "calculateIobArrayInDia()", startPart); + + startPart = new Date(); + MealData mealData = TreatmentsPlugin.getPlugin().getMealData(); + Profiler.log(log, "getMealData()", startPart); + + double maxIob = MainApp.getConstraintChecker().getMaxIOBAllowed().value(); + + minBg = verifyHardLimits(minBg, "minBg", HardLimits.VERY_HARD_LIMIT_MIN_BG[0], HardLimits.VERY_HARD_LIMIT_MIN_BG[1]); + maxBg = verifyHardLimits(maxBg, "maxBg", HardLimits.VERY_HARD_LIMIT_MAX_BG[0], HardLimits.VERY_HARD_LIMIT_MAX_BG[1]); + targetBg = verifyHardLimits(targetBg, "targetBg", HardLimits.VERY_HARD_LIMIT_TARGET_BG[0], HardLimits.VERY_HARD_LIMIT_TARGET_BG[1]); + + boolean isTempTarget = false; + TempTarget tempTarget = TreatmentsPlugin.getPlugin().getTempTargetFromHistory(System.currentTimeMillis()); + if (tempTarget != null) { + isTempTarget = true; + minBg = verifyHardLimits(tempTarget.low, "minBg", HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0], HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1]); + maxBg = verifyHardLimits(tempTarget.high, "maxBg", HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0], HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1]); + targetBg = verifyHardLimits(tempTarget.target(), "targetBg", HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0], HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1]); + } + + + if (!checkOnlyHardLimits(profile.getDia(), "dia", HardLimits.MINDIA, HardLimits.MAXDIA)) + return; + if (!checkOnlyHardLimits(profile.getIcTimeFromMidnight(profile.secondsFromMidnight()), "carbratio", HardLimits.MINIC, HardLimits.MAXIC)) + return; + if (!checkOnlyHardLimits(Profile.toMgdl(profile.getIsf(), units), "sens", HardLimits.MINISF, HardLimits.MAXISF)) + return; + if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.05, HardLimits.maxBasal())) + return; + if (!checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, HardLimits.maxBasal())) + return; + + startPart = new Date(); + if (MainApp.getConstraintChecker().isAutosensModeEnabled().value()) { + lastAutosensResult = IobCobCalculatorPlugin.getPlugin().detectSensitivityWithLock(IobCobCalculatorPlugin.getPlugin().oldestDataAvailable(), System.currentTimeMillis()); + } else { + lastAutosensResult = new AutosensResult(); + } + + Constraint smbAllowed = new Constraint<>(!tempBasalFallback); + MainApp.getConstraintChecker().isSMBModeEnabled(smbAllowed); + inputConstraints.copyReasons(smbAllowed); + + Constraint advancedFiltering = new Constraint<>(!tempBasalFallback); + MainApp.getConstraintChecker().isAdvancedFilteringEnabled(advancedFiltering); + inputConstraints.copyReasons(advancedFiltering); + + Profiler.log(log, "detectSensitivityandCarbAbsorption()", startPart); + Profiler.log(log, "SMB data gathering", start); + + start = new Date(); + try { + determineBasalAdapterSMBJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, ConfigBuilderPlugin.getActivePump().getBaseBasalRate(), iobArray, glucoseStatus, mealData, + lastAutosensResult.ratio, //autosensDataRatio + isTempTarget, + smbAllowed.value(), + advancedFiltering.value() + ); + } catch (JSONException e) { + log.error(e.getMessage()); + return; + } + + long now = System.currentTimeMillis(); + + DetermineBasalResultSMB determineBasalResultSMB = determineBasalAdapterSMBJS.invoke(); + Profiler.log(log, "SMB calculation", start); + // TODO still needed with oref1? + // Fix bug determine basal + if (determineBasalResultSMB.rate == 0d && determineBasalResultSMB.duration == 0 && !TreatmentsPlugin.getPlugin().isTempBasalInProgress()) + determineBasalResultSMB.tempBasalRequested = false; + // limit requests on openloop mode + if (!MainApp.getConstraintChecker().isClosedLoopAllowed().value()) { + TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(now); + if (activeTemp != null && determineBasalResultSMB.rate == 0 && determineBasalResultSMB.duration == 0) { + // going to cancel + } else if (activeTemp != null && Math.abs(determineBasalResultSMB.rate - activeTemp.tempBasalConvertedToAbsolute(now, profile)) < 0.1) { + determineBasalResultSMB.tempBasalRequested = false; + } else if (activeTemp == null && Math.abs(determineBasalResultSMB.rate - ConfigBuilderPlugin.getActivePump().getBaseBasalRate()) < 0.1) + determineBasalResultSMB.tempBasalRequested = false; + } + + determineBasalResultSMB.iob = iobArray[0]; + + try { + determineBasalResultSMB.json.put("timestamp", DateUtil.toISOString(now)); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + + determineBasalResultSMB.inputConstraints = inputConstraints; + + lastDetermineBasalAdapterSMBJS = determineBasalAdapterSMBJS; + lastAPSResult = determineBasalResultSMB; + lastAPSRun = new Date(now); + MainApp.bus().post(new EventOpenAPSUpdateGui()); + + //deviceStatus.suggested = determineBasalResultAMA.json; + } + + // safety checks + private static boolean checkOnlyHardLimits(Double value, String valueName, double lowLimit, double highLimit) { + return value.equals(verifyHardLimits(value, valueName, lowLimit, highLimit)); + } + + private static Double verifyHardLimits(Double value, String valueName, double lowLimit, double highLimit) { + Double newvalue = value; + if (newvalue < lowLimit || newvalue > highLimit) { + newvalue = Math.max(newvalue, lowLimit); + newvalue = Math.min(newvalue, highLimit); + String msg = String.format(MainApp.sResources.getString(R.string.valueoutofrange), valueName); + msg += ".\n"; + msg += String.format(MainApp.sResources.getString(R.string.valuelimitedto), value, newvalue); + log.error(msg); + NSUpload.uploadError(msg); + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), msg, R.raw.error); + } + return newvalue; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/SMBDefaults.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/SMBDefaults.java new file mode 100644 index 0000000000..0ce8d2bb94 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/SMBDefaults.java @@ -0,0 +1,64 @@ +package info.nightscout.androidaps.plugins.OpenAPSSMB; + +/** + * Created by mike on 10.12.2017. + */ + +public class SMBDefaults { + // CALCULATED OR FROM PREFS + + // max_iob: 0 // if max_iob is not provided, will default to zero + // max_daily_safety_multiplier:3 + // current_basal_safety_multiplier:4 + // autosens_max:1.2 + // autosens_min:0.7 + + // USED IN AUTOSENS + public final static boolean rewind_resets_autosens = true; // reset autosensitivity to neutral for awhile after each pump rewind + + // USED IN TARGETS + // by default the higher end of the target range is used only for avoiding bolus wizard overcorrections + // use wide_bg_target_range: true to force neutral temps over a wider range of eventualBGs + public final static boolean wide_bg_target_range = false; // by default use only the low end of the pump's BG target range as OpenAPS target + + // USED IN AUTOTUNE + public final static double autotune_isf_adjustmentFraction = 1.0; // keep autotune ISF closer to pump ISF via a weighted average of fullNewISF and pumpISF. 1.0 allows full adjustment, 0 is no adjustment from pump ISF. + public final static double remainingCarbsFraction = 1.0; // fraction of carbs we'll assume will absorb over 4h if we don't yet see carb absorption + + // USED IN DETERMINE_BASAL + public final static boolean low_temptarget_lowers_sensitivity = false; // lower sensitivity for temptargets <= 99. + public final static boolean high_temptarget_raises_sensitivity = false; // raise sensitivity for temptargets >= 111. synonym for exercise_mode + public final static boolean sensitivity_raises_target = true; // raise BG target when autosens detects sensitivity + public final static boolean resistance_lowers_target = false; // lower BG target when autosens detects resistance + public final static boolean adv_target_adjustments = false; // lower target automatically when BG and eventualBG are high + public final static boolean exercise_mode = false; // when true, > 105 mg/dL high temp target adjusts sensitivityRatio for exercise_mode. This majorly changes the behavior of high temp targets from before. synonmym for high_temptarget_raises_sensitivity + public final static int half_basal_exercise_target = 160; // when temptarget is 160 mg/dL *and* exercise_mode=true, run 50% basal at this level (120 = 75%; 140 = 60%) + // create maxCOB and default it to 120 because that's the most a typical body can absorb over 4 hours. + // (If someone enters more carbs or stacks more; OpenAPS will just truncate dosing based on 120. + // Essentially, this just limits AMA/SMB as a safety cap against excessive COB entry) + public final static int maxCOB = 120; + public final static boolean skip_neutral_temps = true; // ***** default false in oref1 ***** if true, don't set neutral temps + // unsuspend_if_no_temp:false // if true, pump will un-suspend after a zero temp finishes + // bolussnooze_dia_divisor:2 // bolus snooze decays after 1/2 of DIA + public final static double min_5m_carbimpact = 8d; // mg/dL per 5m (8 mg/dL/5m corresponds to 24g/hr at a CSF of 4 mg/dL/g (x/5*60/4)) + public final static int remainingCarbsCap = 90; // max carbs we'll assume will absorb over 4h if we don't yet see carb absorption + // WARNING: use SMB with caution: it can and will automatically bolus up to max_iob worth of extra insulin + // enableUAM:true // enable detection of unannounced meal carb absorption + public final static boolean A52_risk_enable = false; + //public final static boolean enableSMB_with_COB = true; // ***** default false in oref1 ***** enable supermicrobolus while COB is positive + //public final static boolean enableSMB_with_temptarget = true; // ***** default false in oref1 ***** enable supermicrobolus for eating soon temp targets + // *** WARNING *** DO NOT USE enableSMB_always or enableSMB_after_carbs with xDrip+, Libre, or similar + // xDrip+, LimiTTer, etc. do not properly filter out high-noise SGVs + // Using SMB overnight with such data sources risks causing a dangerous overdose of insulin + // if the CGM sensor reads falsely high and doesn't come down as actual BG does + // public final static boolean enableSMB_always = false; // always enable supermicrobolus (unless disabled by high temptarget) + // *** WARNING *** DO NOT USE enableSMB_always or enableSMB_after_carbs with xDrip+, Libre, or similar + //public final static boolean enableSMB_after_carbs = false; // enable supermicrobolus for 6h after carbs, even with 0 COB + //public final static boolean allowSMB_with_high_temptarget = false; // allow supermicrobolus (if otherwise enabled) even with high temp targets + public final static int maxSMBBasalMinutes = 30; // maximum minutes of basal that can be delivered as a single SMB with uncovered COB + // curve:"rapid-acting" // Supported curves: "bilinear", "rapid-acting" (Novolog, Novorapid, Humalog, Apidra) and "ultra-rapid" (Fiasp) + // useCustomPeakTime:false // allows changing insulinPeakTime + // insulinPeakTime:75 // number of minutes after a bolus activity peaks. defaults to 55m for Fiasp if useCustomPeakTime: false + public final static int carbsReqThreshold = 1; // grams of carbsReq to trigger a pushover + // offline_hotspot:false // enabled an offline-only local wifi hotspot if no Internet available +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/CalibrationDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/CalibrationDialog.java index d5053c9317..f0ccdd9809 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/CalibrationDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/CalibrationDialog.java @@ -11,7 +11,6 @@ import android.view.Window; import android.view.WindowManager; import android.widget.TextView; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import org.slf4j.Logger; @@ -24,6 +23,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.GlucoseStatus; import info.nightscout.androidaps.data.Profile; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.NumberPicker; import info.nightscout.utils.SafeParse; import info.nightscout.utils.XdripCalibrations; @@ -88,7 +88,7 @@ public class CalibrationDialog extends DialogFragment implements View.OnClickLis final Double bg = SafeParse.stringToDouble(bgNumber.getText()); XdripCalibrations.confirmAndSendCalibration(bg, context); dismiss(); - Answers.getInstance().logCustom(new CustomEvent("Calibration")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("Calibration")); break; case R.id.cancel: dismiss(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/ErrorDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/ErrorDialog.java index f11283c029..7d45c35792 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/ErrorDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/ErrorDialog.java @@ -19,6 +19,7 @@ import info.nightscout.androidaps.Services.AlarmSoundService; public class ErrorDialog extends DialogFragment implements View.OnClickListener { private static Logger log = LoggerFactory.getLogger(ErrorDialog.class); + Button muteButton; Button okButton; TextView statusView; ErrorHelperActivity helperActivity; @@ -52,14 +53,14 @@ public class ErrorDialog extends DialogFragment implements View.OnClickListener Bundle savedInstanceState) { getDialog().setTitle(title); View view = inflater.inflate(R.layout.overview_error_dialog, container, false); + muteButton = (Button) view.findViewById(R.id.overview_error_mute); okButton = (Button) view.findViewById(R.id.overview_error_ok); statusView = (TextView) view.findViewById(R.id.overview_error_status); + muteButton.setOnClickListener(this); okButton.setOnClickListener(this); setCancelable(false); - Intent alarm = new Intent(MainApp.instance().getApplicationContext(), AlarmSoundService.class); - alarm.putExtra("soundid", soundId); - MainApp.instance().startService(alarm); + startAlarm(); return view; } @@ -77,13 +78,16 @@ public class ErrorDialog extends DialogFragment implements View.OnClickListener if (helperActivity != null) { helperActivity.finish(); } - Intent alarm = new Intent(MainApp.instance().getApplicationContext(), AlarmSoundService.class); - MainApp.instance().stopService(alarm); + stopAlarm(); } @Override public void onClick(View view) { switch (view.getId()) { + case R.id.overview_error_mute: + log.debug("Error dialog mute button pressed"); + stopAlarm(); + break; case R.id.overview_error_ok: log.debug("Error dialog ok button pressed"); dismiss(); @@ -91,4 +95,14 @@ public class ErrorDialog extends DialogFragment implements View.OnClickListener } } + private void startAlarm() { + Intent alarm = new Intent(MainApp.instance().getApplicationContext(), AlarmSoundService.class); + alarm.putExtra("soundid", soundId); + MainApp.instance().startService(alarm); + } + + private void stopAlarm() { + Intent alarm = new Intent(MainApp.instance().getApplicationContext(), AlarmSoundService.class); + MainApp.instance().stopService(alarm); + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/ErrorHelperActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/ErrorHelperActivity.java index 259c9c76eb..90cf9aecd5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/ErrorHelperActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/ErrorHelperActivity.java @@ -3,6 +3,10 @@ package info.nightscout.androidaps.plugins.Overview.Dialogs; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; +import info.nightscout.androidaps.R; +import info.nightscout.utils.NSUpload; +import info.nightscout.utils.SP; + public class ErrorHelperActivity extends AppCompatActivity { public ErrorHelperActivity() { super(); @@ -17,5 +21,9 @@ public class ErrorHelperActivity extends AppCompatActivity { errorDialog.setSound(getIntent().getIntExtra("soundid", 0)); errorDialog.setTitle(getIntent().getStringExtra("title")); errorDialog.show(this.getSupportFragmentManager(), "Error"); + + if (SP.getBoolean(R.string.key_ns_create_announcements_from_errors, true)) { + NSUpload.uploadError(getIntent().getStringExtra("status")); + } } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewCarbsDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewCarbsDialog.java new file mode 100644 index 0000000000..5939ab5aa3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewCarbsDialog.java @@ -0,0 +1,449 @@ +package info.nightscout.androidaps.plugins.Overview.Dialogs; + +import android.content.Intent; +import android.os.Bundle; +import android.os.HandlerThread; +import android.support.annotation.Nullable; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.text.Editable; +import android.text.Html; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.RadioButton; + +import com.google.common.base.Joiner; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.DecimalFormat; +import java.util.LinkedList; +import java.util.List; + +import info.nightscout.androidaps.Constants; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.db.CareportalEvent; +import info.nightscout.androidaps.db.Source; +import info.nightscout.androidaps.db.TempTarget; +import info.nightscout.androidaps.interfaces.Constraint; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; +import info.nightscout.androidaps.queue.Callback; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.DefaultValueHelper; +import info.nightscout.utils.NumberPicker; +import info.nightscout.utils.SP; +import info.nightscout.utils.ToastUtils; + +import static info.nightscout.utils.DateUtil.now; + +public class NewCarbsDialog extends DialogFragment implements OnClickListener, CompoundButton.OnCheckedChangeListener { + private static Logger log = LoggerFactory.getLogger(NewCarbsDialog.class); + + private static final int FAV1_DEFAULT = 5; + private static final int FAV2_DEFAULT = 10; + private static final int FAV3_DEFAULT = 20; + + private RadioButton startActivityTTCheckbox; + private RadioButton startEatingSoonTTCheckbox; + private RadioButton startHypoTTCheckbox; + private boolean togglingTT; + + private NumberPicker editTime; + private NumberPicker editDuration; + private NumberPicker editCarbs; + private Integer maxCarbs; + + private EditText notesEdit; + + //one shot guards + private boolean accepted; + private boolean okClicked; + + public NewCarbsDialog() { + HandlerThread mHandlerThread = new HandlerThread(NewCarbsDialog.class.getSimpleName()); + mHandlerThread.start(); + } + + final private TextWatcher textWatcher = new TextWatcher() { + @Override + public void afterTextChanged(Editable s) { + validateInputs(); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + }; + + private void validateInputs() { + int time = editTime.getValue().intValue(); + if (time > 12 * 60 || time < -12 * 60) { + editTime.setValue(0d); + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.constraintapllied)); + } + Double duration = editDuration.getValue(); + if (duration > 10) { + editDuration.setValue(0d); + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.constraintapllied)); + } + int carbs = editCarbs.getValue().intValue(); + if (carbs > maxCarbs) { + editCarbs.setValue(0d); + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.carbsconstraintapplied)); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.overview_newcarbs_dialog, container, false); + + view.findViewById(R.id.ok).setOnClickListener(this); + view.findViewById(R.id.cancel).setOnClickListener(this); + + getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE); + getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + + startActivityTTCheckbox = view.findViewById(R.id.newcarbs_activity_tt); + startActivityTTCheckbox.setOnCheckedChangeListener(this); + startEatingSoonTTCheckbox = view.findViewById(R.id.newcarbs_eating_soon_tt); + startEatingSoonTTCheckbox.setOnCheckedChangeListener(this); + startHypoTTCheckbox = view.findViewById(R.id.newcarbs_hypo_tt); + startHypoTTCheckbox.setOnCheckedChangeListener(this); + + editTime = view.findViewById(R.id.newcarbs_time); + editTime.setParams(0d, -12 * 60d, 12 * 60d, 5d, new DecimalFormat("0"), false, textWatcher); + + editDuration = view.findViewById(R.id.new_carbs_duration); + editDuration.setParams(0d, 0d, 10d, 1d, new DecimalFormat("0"), false, textWatcher); + + maxCarbs = MainApp.getConstraintChecker().getMaxCarbsAllowed().value(); + + editCarbs = view.findViewById(R.id.newcarb_carbsamount); + editCarbs.setParams(0d, 0d, (double) maxCarbs, 1d, new DecimalFormat("0"), false, textWatcher); + + Button fav1Button = view.findViewById(R.id.newcarbs_plus1); + fav1Button.setOnClickListener(this); + fav1Button.setText(toSignedString(SP.getInt(R.string.key_carbs_button_increment_1, FAV1_DEFAULT))); + + Button fav2Button = view.findViewById(R.id.newcarbs_plus2); + fav2Button.setOnClickListener(this); + fav2Button.setText(toSignedString(SP.getInt(R.string.key_carbs_button_increment_2, FAV2_DEFAULT))); + + Button fav3Button = view.findViewById(R.id.newcarbs_plus3); + fav3Button.setOnClickListener(this); + fav3Button.setText(toSignedString(SP.getInt(R.string.key_carbs_button_increment_3, FAV3_DEFAULT))); + + LinearLayout notesLayout = view.findViewById(R.id.newcarbs_notes_layout); + notesLayout.setVisibility(SP.getBoolean(R.string.key_show_notes_entry_dialogs, false) ? View.VISIBLE : View.GONE); + notesEdit = view.findViewById(R.id.newcarbs_notes); + + setCancelable(true); + getDialog().setCanceledOnTouchOutside(false); + return view; + } + + private String toSignedString(int value) { + return value > 0 ? "+" + value : String.valueOf(value); + } + + @Override + public synchronized void onClick(View view) { + switch (view.getId()) { + case R.id.ok: + submit(); + break; + case R.id.cancel: + dismiss(); + break; + case R.id.newcarbs_plus1: + editCarbs.setValue(Math.max(0, editCarbs.getValue() + + SP.getInt(R.string.key_carbs_button_increment_1, FAV1_DEFAULT))); + validateInputs(); + break; + case R.id.newcarbs_plus2: + editCarbs.setValue(Math.max(0, editCarbs.getValue() + + SP.getInt(R.string.key_carbs_button_increment_2, FAV2_DEFAULT))); + validateInputs(); + break; + case R.id.newcarbs_plus3: + editCarbs.setValue(Math.max(0, editCarbs.getValue() + + SP.getInt(R.string.key_carbs_button_increment_3, FAV3_DEFAULT))); + validateInputs(); + break; + case R.id.newcarbs_activity_tt: + if (togglingTT) { + togglingTT = false; + break; + } + startActivityTTCheckbox.setOnClickListener(null); + startActivityTTCheckbox.setOnCheckedChangeListener(null); + startActivityTTCheckbox.setChecked(false); + startActivityTTCheckbox.setOnCheckedChangeListener(this); + break; + case R.id.newcarbs_eating_soon_tt: + if (togglingTT) { + togglingTT = false; + break; + } + startEatingSoonTTCheckbox.setOnClickListener(null); + startEatingSoonTTCheckbox.setOnCheckedChangeListener(null); + startEatingSoonTTCheckbox.setChecked(false); + startEatingSoonTTCheckbox.setOnCheckedChangeListener(this); + break; + case R.id.newcarbs_hypo_tt: + if (togglingTT) { + togglingTT = false; + break; + } + startHypoTTCheckbox.setOnClickListener(null); + startHypoTTCheckbox.setOnCheckedChangeListener(null); + startHypoTTCheckbox.setChecked(false); + startHypoTTCheckbox.setOnCheckedChangeListener(this); + break; + } + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + // Logic to disable a selected radio when pressed. When a checked radio + // is pressed, no CheckChanged event is trigger, so register a Click event + // when checking a radio. Since Click events come after CheckChanged events, + // the Click event is triggered immediately after this. Thus, set toggingTT + // var to true, so that the first Click event fired after this is ignored. + // Radios remove themselves from Click events once unchecked. + // Since radios are not in a group, manually update their state. + switch (buttonView.getId()) { + case R.id.newcarbs_activity_tt: + togglingTT = true; + startActivityTTCheckbox.setOnClickListener(this); + + startEatingSoonTTCheckbox.setOnCheckedChangeListener(null); + startEatingSoonTTCheckbox.setChecked(false); + startEatingSoonTTCheckbox.setOnCheckedChangeListener(this); + + startHypoTTCheckbox.setOnCheckedChangeListener(null); + startHypoTTCheckbox.setChecked(false); + startHypoTTCheckbox.setOnCheckedChangeListener(this); + break; + case R.id.newcarbs_eating_soon_tt: + togglingTT = true; + startEatingSoonTTCheckbox.setOnClickListener(this); + + startActivityTTCheckbox.setOnCheckedChangeListener(null); + startActivityTTCheckbox.setChecked(false); + startActivityTTCheckbox.setOnCheckedChangeListener(this); + + startHypoTTCheckbox.setOnCheckedChangeListener(null); + startHypoTTCheckbox.setChecked(false); + startHypoTTCheckbox.setOnCheckedChangeListener(this); + break; + case R.id.newcarbs_hypo_tt: + togglingTT = true; + startHypoTTCheckbox.setOnClickListener(this); + + startActivityTTCheckbox.setOnCheckedChangeListener(null); + startActivityTTCheckbox.setChecked(false); + startActivityTTCheckbox.setOnCheckedChangeListener(this); + + startEatingSoonTTCheckbox.setOnCheckedChangeListener(null); + startEatingSoonTTCheckbox.setChecked(false); + startEatingSoonTTCheckbox.setOnCheckedChangeListener(this); + break; + } + } + + private void submit() { + if (okClicked) { + log.debug("guarding: ok already clicked"); + dismiss(); + return; + } + okClicked = true; + try { + final Profile currentProfile = MainApp.getConfigBuilder().getProfile(); + if (currentProfile == null) { + return; + } + + int carbs = editCarbs.getValue().intValue(); + Integer carbsAfterConstraints = MainApp.getConstraintChecker().applyCarbsConstraints(new Constraint<>(carbs)).value(); + + final String units = currentProfile.getUnits(); + DefaultValueHelper helper = new DefaultValueHelper(); + + int activityTTDuration = helper.determineActivityTTDuration(); + double activityTT = helper.determineActivityTT(units); + + int eatingSoonTTDuration = helper.determineEatingSoonTTDuration(); + double eatingSoonTT = helper.determineEatingSoonTT(units); + + int hypoTTDuration = helper.determineHypoTTDuration(); + double hypoTT = helper.determineHypoTT(units); + + List actions = new LinkedList<>(); + + if (startActivityTTCheckbox.isChecked()) { + String unitLabel = "mg/dl"; + if (currentProfile.getUnits().equals(Constants.MMOL)) { + unitLabel = "mmol/l"; + } + + actions.add(MainApp.gs(R.string.temptargetshort) + ": " + "" + DecimalFormatter.to1Decimal(activityTT) + " " + unitLabel + " (" + activityTTDuration + " min)"); + + } + if (startEatingSoonTTCheckbox.isChecked()) { + if (currentProfile.getUnits().equals(Constants.MMOL)) { + actions.add(MainApp.gs(R.string.temptargetshort) + ": " + "" + DecimalFormatter.to1Decimal(eatingSoonTT) + " mmol/l (" + eatingSoonTTDuration + " min)"); + } else + actions.add(MainApp.gs(R.string.temptargetshort) + ": " + "" + DecimalFormatter.to0Decimal(eatingSoonTT) + " mg/dl (" + eatingSoonTTDuration + " min)"); + } + if (startHypoTTCheckbox.isChecked()) { + if (currentProfile.getUnits().equals(Constants.MMOL)) { + actions.add(MainApp.gs(R.string.temptargetshort) + ": " + "" + DecimalFormatter.to1Decimal(hypoTT) + " mmol/l (" + hypoTTDuration + " min)"); + } else + actions.add(MainApp.gs(R.string.temptargetshort) + ": " + "" + DecimalFormatter.to0Decimal(hypoTT) + " mg/dl (" + hypoTTDuration + " min)"); + } + + int timeOffset = editTime.getValue().intValue(); + final long time = now() + timeOffset * 1000 * 60; + if (timeOffset != 0) { + actions.add(MainApp.gs(R.string.time) + ": " + DateUtil.dateAndTimeString(time)); + } + int duration = editDuration.getValue().intValue(); + if (duration > 0) { + actions.add(MainApp.gs(R.string.duration) + ": " + duration + MainApp.gs(R.string.shorthour)); + } + if (carbs > 0) { + actions.add(MainApp.gs(R.string.carbs) + ": " + "" + carbsAfterConstraints + "g" + ""); + } + if (!carbsAfterConstraints.equals(carbs)) { + actions.add("" + MainApp.gs(R.string.carbsconstraintapplied) + ""); + } + final String notes = notesEdit.getText().toString(); + if (!notes.isEmpty()) { + actions.add(MainApp.gs(R.string.careportal_newnstreatment_notes_label) + ": " + notes); + } + + final double finalActivityTT = activityTT; + final int finalActivityTTDuration = activityTTDuration; + final double finalEatigSoonTT = eatingSoonTT; + final int finalEatingSoonTTDuration = eatingSoonTTDuration; + final double finalHypoTT = hypoTT; + final int finalHypoTTDuration = hypoTTDuration; + + final AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setTitle(MainApp.gs(R.string.confirmation)); + if (carbsAfterConstraints > 0 || startActivityTTCheckbox.isChecked() + || startEatingSoonTTCheckbox.isChecked() || startHypoTTCheckbox.isChecked()) { + builder.setMessage(Html.fromHtml(Joiner.on("
").join(actions))); + builder.setPositiveButton(MainApp.gs(R.string.ok), (dialog, id) -> { + synchronized (builder) { + if (accepted) { + log.debug("guarding: already accepted"); + return; + } + accepted = true; + + if (startActivityTTCheckbox.isChecked()) { + TempTarget tempTarget = new TempTarget() + .date(System.currentTimeMillis()) + .duration(finalActivityTTDuration) + .reason(MainApp.gs(R.string.activity)) + .source(Source.USER) + .low(Profile.toMgdl(finalActivityTT, currentProfile.getUnits())) + .high(Profile.toMgdl(finalActivityTT, currentProfile.getUnits())); + TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget); + } else if (startEatingSoonTTCheckbox.isChecked()) { + TempTarget tempTarget = new TempTarget() + .date(System.currentTimeMillis()) + .duration(finalEatingSoonTTDuration) + .reason(MainApp.gs(R.string.eatingsoon)) + .source(Source.USER) + .low(Profile.toMgdl(finalEatigSoonTT, currentProfile.getUnits())) + .high(Profile.toMgdl(finalEatigSoonTT, currentProfile.getUnits())); + TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget); + } else if (startHypoTTCheckbox.isChecked()) { + TempTarget tempTarget = new TempTarget() + .date(System.currentTimeMillis()) + .duration(finalHypoTTDuration) + .reason(MainApp.gs(R.string.hypo)) + .source(Source.USER) + .low(Profile.toMgdl(finalHypoTT, currentProfile.getUnits())) + .high(Profile.toMgdl(finalHypoTT, currentProfile.getUnits())); + TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget); + } + + if (carbsAfterConstraints > 0) { + if (duration == 0) { + createCarb(carbsAfterConstraints, time, notes); + } else { + long remainingCarbs = carbsAfterConstraints; + int ticks = (duration * 4); //duration guaranteed to be integer greater zero + for (int i = 0; i < ticks; i++){ + long carbTime = time + i * 15 * 60 * 1000; + long smallCarbAmount = Math.round((1d * remainingCarbs) / (ticks-i)); //on last iteration (ticks-i) is 1 -> smallCarbAmount == remainingCarbs + remainingCarbs -= smallCarbAmount; + createCarb(smallCarbAmount, carbTime, notes); + } + } + } + } + }); + } else { + builder.setMessage(MainApp.gs(R.string.no_action_selected)); + } + builder.setNegativeButton(MainApp.gs(R.string.cancel), null); + builder.show(); + dismiss(); + } catch (Exception e) { + log.error("Unhandled exception", e); + } + } + + private void createCarb(long carbs, long time, @Nullable String notes) { + DetailedBolusInfo carbInfo = new DetailedBolusInfo(); + carbInfo.date = time; + carbInfo.eventType = CareportalEvent.CARBCORRECTION; + carbInfo.carbs = carbs; + carbInfo.context = getContext(); + carbInfo.source = Source.USER; + carbInfo.notes = notes; + if (ConfigBuilderPlugin.getActivePump().getPumpDescription().storesCarbInfo && carbInfo.date <= now()) { + ConfigBuilderPlugin.getCommandQueue().bolus(carbInfo, new Callback() { + @Override + public void run() { + if (!result.success) { + Intent i = new Intent(MainApp.instance(), ErrorHelperActivity.class); + i.putExtra("soundid", R.raw.boluserror); + i.putExtra("status", result.comment); + i.putExtra("title", MainApp.gs(R.string.treatmentdeliveryerror)); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + MainApp.instance().startActivity(i); + } + } + }); + } else { + TreatmentsPlugin.getPlugin().addToHistoryTreatment(carbInfo); + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewInsulinDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewInsulinDialog.java new file mode 100644 index 0000000000..3539bee093 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewInsulinDialog.java @@ -0,0 +1,304 @@ +package info.nightscout.androidaps.plugins.Overview.Dialogs; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.HandlerThread; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.text.Editable; +import android.text.Html; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.LinearLayout; + +import com.crashlytics.android.answers.CustomEvent; +import com.google.common.base.Joiner; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.DecimalFormat; +import java.util.LinkedList; +import java.util.List; + +import info.nightscout.androidaps.Constants; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.db.CareportalEvent; +import info.nightscout.androidaps.db.Source; +import info.nightscout.androidaps.db.TempTarget; +import info.nightscout.androidaps.interfaces.Constraint; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; +import info.nightscout.androidaps.queue.Callback; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.FabricPrivacy; +import info.nightscout.utils.NumberPicker; +import info.nightscout.utils.SP; +import info.nightscout.utils.SafeParse; +import info.nightscout.utils.ToastUtils; + +import static info.nightscout.utils.DateUtil.now; + +public class NewInsulinDialog extends DialogFragment implements OnClickListener { + private static Logger log = LoggerFactory.getLogger(NewInsulinDialog.class); + + public static final double PLUS1_DEFAULT = 0.5d; + public static final double PLUS2_DEFAULT = 1d; + public static final double PLUS3_DEFAULT = 2d; + + private CheckBox startEatingSoonTTCheckbox; + private CheckBox recordOnlyCheckbox; + + private LinearLayout editLayout; + private NumberPicker editTime; + private NumberPicker editInsulin; + private Double maxInsulin; + + private EditText notesEdit; + + //one shot guards + private boolean accepted; + private boolean okClicked; + + public NewInsulinDialog() { + HandlerThread mHandlerThread = new HandlerThread(NewInsulinDialog.class.getSimpleName()); + mHandlerThread.start(); + } + + final private TextWatcher textWatcher = new TextWatcher() { + @Override + public void afterTextChanged(Editable s) { + validateInputs(); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + }; + + private void validateInputs() { + int time = editTime.getValue().intValue(); + if (Math.abs(time) > 12 * 60) { + editTime.setValue(0d); + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.constraintapllied)); + } + Double insulin = editInsulin.getValue(); + if (insulin > maxInsulin) { + editInsulin.setValue(0d); + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.bolusconstraintapplied)); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.overview_newinsulin_dialog, container, false); + + view.findViewById(R.id.ok).setOnClickListener(this); + view.findViewById(R.id.cancel).setOnClickListener(this); + + getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE); + getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + + startEatingSoonTTCheckbox = view.findViewById(R.id.newinsulin_start_eating_soon_tt); + + recordOnlyCheckbox = view.findViewById(R.id.newinsulin_record_only); + recordOnlyCheckbox.setOnCheckedChangeListener((buttonView, isChecked) -> editLayout.setVisibility(isChecked ? View.VISIBLE : View.GONE)); + + editLayout = view.findViewById(R.id.newinsulin_time_layout); + editLayout.setVisibility(View.GONE); + editTime = view.findViewById(R.id.newinsulin_time); + editTime.setParams(0d, -12 * 60d, 12 * 60d, 5d, new DecimalFormat("0"), false, textWatcher); + + maxInsulin = MainApp.getConstraintChecker().getMaxBolusAllowed().value(); + + editInsulin = view.findViewById(R.id.newinsulin_amount); + editInsulin.setParams(0d, 0d, maxInsulin, ConfigBuilderPlugin.getActivePump().getPumpDescription().bolusStep, DecimalFormatter.pumpSupportedBolusFormat(), false, textWatcher); + + Button plus1Button = view.findViewById(R.id.newinsulin_plus05); + plus1Button.setOnClickListener(this); + plus1Button.setText(toSignedString(SP.getDouble(MainApp.gs(R.string.key_insulin_button_increment_1), PLUS1_DEFAULT))); + Button plus2Button = view.findViewById(R.id.newinsulin_plus10); + plus2Button.setOnClickListener(this); + plus2Button.setText(toSignedString(SP.getDouble(MainApp.gs(R.string.key_insulin_button_increment_2), PLUS2_DEFAULT))); + Button plus3Button = view.findViewById(R.id.newinsulin_plus20); + plus3Button.setOnClickListener(this); + plus3Button.setText(toSignedString(SP.getDouble(MainApp.gs(R.string.key_insulin_button_increment_3), PLUS3_DEFAULT))); + + LinearLayout notesLayout = view.findViewById(R.id.newinsulin_notes_layout); + notesLayout.setVisibility(SP.getBoolean(R.string.key_show_notes_entry_dialogs, false) ? View.VISIBLE : View.GONE); + notesEdit = view.findViewById(R.id.newinsulin_notes); + + setCancelable(true); + getDialog().setCanceledOnTouchOutside(false); + return view; + } + + private String toSignedString(double value) { + String formatted = DecimalFormatter.toPumpSupportedBolus(value); + return value > 0 ? "+" + formatted : formatted; + } + + @Override + public synchronized void onClick(View view) { + switch (view.getId()) { + case R.id.ok: + submit(); + break; + case R.id.cancel: + dismiss(); + break; + case R.id.newinsulin_plus05: + editInsulin.setValue(Math.max(0, editInsulin.getValue() + + SP.getDouble(MainApp.gs(R.string.key_insulin_button_increment_1), PLUS1_DEFAULT))); + validateInputs(); + break; + case R.id.newinsulin_plus10: + editInsulin.setValue(Math.max(0, editInsulin.getValue() + + SP.getDouble(MainApp.gs(R.string.key_insulin_button_increment_2), PLUS2_DEFAULT))); + validateInputs(); + break; + case R.id.newinsulin_plus20: + editInsulin.setValue(Math.max(0, editInsulin.getValue() + + SP.getDouble(MainApp.gs(R.string.key_insulin_button_increment_3), PLUS3_DEFAULT))); + validateInputs(); + break; + } + } + + private void submit() { + if (okClicked) { + log.debug("guarding: ok already clicked"); + dismiss(); + return; + } + okClicked = true; + + try { + Profile currentProfile = MainApp.getConfigBuilder().getProfile(); + if (currentProfile == null) + return; + + Double insulin = SafeParse.stringToDouble(editInsulin.getText()); + Double insulinAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(insulin)).value(); + + List actions = new LinkedList<>(); + if (insulin > 0) { + actions.add(MainApp.gs(R.string.bolus) + ": " + "" + insulinAfterConstraints + "U" + ""); + if (recordOnlyCheckbox.isChecked()) { + actions.add("" + MainApp.gs(R.string.bolusrecordedonly) + ""); + } + } + + if (!insulinAfterConstraints.equals(insulin)) + actions.add("" + MainApp.gs(R.string.bolusconstraintapplied) + ""); + + int eatingSoonTTDuration = SP.getInt(R.string.key_eatingsoon_duration, Constants.defaultEatingSoonTTDuration); + eatingSoonTTDuration = eatingSoonTTDuration > 0 ? eatingSoonTTDuration : Constants.defaultEatingSoonTTDuration; + double eatingSoonTT = SP.getDouble(R.string.key_eatingsoon_target, currentProfile.getUnits().equals(Constants.MMOL) ? Constants.defaultEatingSoonTTmmol : Constants.defaultEatingSoonTTmgdl); + eatingSoonTT = eatingSoonTT > 0 ? eatingSoonTT : currentProfile.getUnits().equals(Constants.MMOL) ? Constants.defaultEatingSoonTTmmol : Constants.defaultEatingSoonTTmgdl; + + if (startEatingSoonTTCheckbox.isChecked()) { + if (currentProfile.getUnits().equals(Constants.MMOL)) { + actions.add(MainApp.gs(R.string.temptargetshort) + ": " + "" + DecimalFormatter.to1Decimal(eatingSoonTT) + " mmol/l (" + eatingSoonTTDuration + " min)"); + } else + actions.add(MainApp.gs(R.string.temptargetshort) + ": " + "" + DecimalFormatter.to0Decimal(eatingSoonTT) + " mg/dl (" + eatingSoonTTDuration + " min)"); + } + + int timeOffset = editTime.getValue().intValue(); + final long time = now() + timeOffset * 1000 * 60; + if (timeOffset != 0) { + actions.add(MainApp.gs(R.string.time) + ": " + DateUtil.dateAndTimeString(time)); + } + final String notes = notesEdit.getText().toString(); + if (!notes.isEmpty()) { + actions.add(MainApp.gs(R.string.careportal_newnstreatment_notes_label) + ": " + notes); + } + + final double finalInsulinAfterConstraints = insulinAfterConstraints; + final double finalEatigSoonTT = eatingSoonTT; + final int finalEatingSoonTTDuration = eatingSoonTTDuration; + + final Context context = getContext(); + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + + builder.setTitle(MainApp.gs(R.string.confirmation)); + if (finalInsulinAfterConstraints > 0 || startEatingSoonTTCheckbox.isChecked()) { + builder.setMessage(Html.fromHtml(Joiner.on("
").join(actions))); + builder.setPositiveButton(MainApp.gs(R.string.ok), (dialog, id) -> { + synchronized (builder) { + if (accepted) { + log.debug("guarding: already accepted"); + return; + } + accepted = true; + + if (startEatingSoonTTCheckbox.isChecked()) { + TempTarget tempTarget = new TempTarget() + .date(System.currentTimeMillis()) + .duration(finalEatingSoonTTDuration) + .reason(MainApp.gs(R.string.eatingsoon)) + .source(Source.USER) + .low(Profile.toMgdl(finalEatigSoonTT, currentProfile.getUnits())) + .high(Profile.toMgdl(finalEatigSoonTT, currentProfile.getUnits())); + TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget); + } + + if (finalInsulinAfterConstraints > 0) { + DetailedBolusInfo detailedBolusInfo = new DetailedBolusInfo(); + detailedBolusInfo.eventType = CareportalEvent.CORRECTIONBOLUS; + detailedBolusInfo.insulin = finalInsulinAfterConstraints; + detailedBolusInfo.context = context; + detailedBolusInfo.source = Source.USER; + detailedBolusInfo.notes = notes; + if (recordOnlyCheckbox.isChecked()) { + detailedBolusInfo.date = time; + TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo); + } else { + detailedBolusInfo.date = now(); + ConfigBuilderPlugin.getCommandQueue().bolus(detailedBolusInfo, new Callback() { + @Override + public void run() { + if (!result.success) { + Intent i = new Intent(MainApp.instance(), ErrorHelperActivity.class); + i.putExtra("soundid", R.raw.boluserror); + i.putExtra("status", result.comment); + i.putExtra("title", MainApp.gs(R.string.treatmentdeliveryerror)); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + MainApp.instance().startActivity(i); + } + } + }); + FabricPrivacy.getInstance().logCustom(new CustomEvent("Bolus")); + } + } + } + }); + } else { + builder.setMessage(MainApp.gs(R.string.no_action_selected)); + } + builder.setNegativeButton(MainApp.gs(R.string.cancel), null); + builder.show(); + dismiss(); + } catch (Exception e) { + log.error("Unhandled exception", e); + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewTreatmentDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewTreatmentDialog.java index 69fbfdc149..a315310dc8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewTreatmentDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewTreatmentDialog.java @@ -15,8 +15,8 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; +import android.widget.CheckBox; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import org.slf4j.Logger; @@ -31,9 +31,12 @@ import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; import info.nightscout.androidaps.db.CareportalEvent; import info.nightscout.androidaps.db.Source; -import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.androidaps.queue.Callback; +import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.NumberPicker; import info.nightscout.utils.SafeParse; import info.nightscout.utils.ToastUtils; @@ -51,6 +54,8 @@ public class NewTreatmentDialog extends DialogFragment implements OnClickListene private boolean accepted; private boolean okClicked; + private CheckBox recordOnlyCheckbox; + public NewTreatmentDialog() { } @@ -73,12 +78,12 @@ public class NewTreatmentDialog extends DialogFragment implements OnClickListene Integer carbs = SafeParse.stringToInt(editCarbs.getText()); if (carbs > maxCarbs) { editCarbs.setValue(0d); - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), getString(R.string.carbsconstraintapplied)); + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.carbsconstraintapplied)); } Double insulin = SafeParse.stringToDouble(editInsulin.getText()); if (insulin > maxInsulin) { editInsulin.setValue(0d); - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), getString(R.string.bolusconstraintapplied)); + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.bolusconstraintapplied)); } } @@ -93,14 +98,16 @@ public class NewTreatmentDialog extends DialogFragment implements OnClickListene getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE); getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); - maxCarbs = MainApp.getConfigBuilder().applyCarbsConstraints(Constants.carbsOnlyForCheckLimit); - maxInsulin = MainApp.getConfigBuilder().applyBolusConstraints(Constants.bolusOnlyForCheckLimit); + maxCarbs = MainApp.getConstraintChecker().getMaxCarbsAllowed().value(); + maxInsulin = MainApp.getConstraintChecker().getMaxBolusAllowed().value(); editCarbs = (NumberPicker) view.findViewById(R.id.treatments_newtreatment_carbsamount); editInsulin = (NumberPicker) view.findViewById(R.id.treatments_newtreatment_insulinamount); editCarbs.setParams(0d, 0d, (double) maxCarbs, 1d, new DecimalFormat("0"), false, textWatcher); - editInsulin.setParams(0d, 0d, maxInsulin, ConfigBuilderPlugin.getActivePump().getPumpDescription().bolusStep, new DecimalFormat("0.00"), false, textWatcher); + editInsulin.setParams(0d, 0d, maxInsulin, ConfigBuilderPlugin.getActivePump().getPumpDescription().bolusStep, DecimalFormatter.pumpSupportedBolusFormat(), false, textWatcher); + + recordOnlyCheckbox = (CheckBox) view.findViewById(R.id.newtreatment_record_only); setCancelable(true); getDialog().setCanceledOnTouchOutside(false); @@ -122,15 +129,21 @@ public class NewTreatmentDialog extends DialogFragment implements OnClickListene Double insulin = SafeParse.stringToDouble(editInsulin.getText()); final Integer carbs = SafeParse.stringToInt(editCarbs.getText()); - String confirmMessage = getString(R.string.entertreatmentquestion) + "
"; + String confirmMessage = MainApp.gs(R.string.entertreatmentquestion) + "
"; - Double insulinAfterConstraints = MainApp.getConfigBuilder().applyBolusConstraints(insulin); - Integer carbsAfterConstraints = MainApp.getConfigBuilder().applyCarbsConstraints(carbs); + Double insulinAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(insulin)).value(); + Integer carbsAfterConstraints = MainApp.getConstraintChecker().applyCarbsConstraints(new Constraint<>(carbs)).value(); - confirmMessage += getString(R.string.bolus) + ": " + "" + insulinAfterConstraints + "U" + ""; - confirmMessage += "
" + getString(R.string.carbs) + ": " + carbsAfterConstraints + "g"; + if (insulin > 0) { + confirmMessage += MainApp.gs(R.string.bolus) + ": " + "" + insulinAfterConstraints + "U" + ""; + if (recordOnlyCheckbox.isChecked()) { + confirmMessage += "
" + MainApp.gs(R.string.bolusrecordedonly) + ""; + } + } + if (carbsAfterConstraints > 0) + confirmMessage += "
" + MainApp.gs(R.string.carbs) + ": " + carbsAfterConstraints + "g"; if (insulinAfterConstraints - insulin != 0 || !Objects.equals(carbsAfterConstraints, carbs)) - confirmMessage += "
" + getString(R.string.constraintapllied); + confirmMessage += "
" + MainApp.gs(R.string.constraintapllied); final double finalInsulinAfterConstraints = insulinAfterConstraints; @@ -139,9 +152,9 @@ public class NewTreatmentDialog extends DialogFragment implements OnClickListene final Context context = getContext(); final AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(this.getContext().getString(R.string.confirmation)); + builder.setTitle(MainApp.gs(R.string.confirmation)); builder.setMessage(Html.fromHtml(confirmMessage)); - builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() { + builder.setPositiveButton(MainApp.gs(R.string.ok), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { synchronized (builder) { if (accepted) { @@ -159,7 +172,7 @@ public class NewTreatmentDialog extends DialogFragment implements OnClickListene detailedBolusInfo.carbs = finalCarbsAfterConstraints; detailedBolusInfo.context = context; detailedBolusInfo.source = Source.USER; - if (detailedBolusInfo.insulin > 0 || ConfigBuilderPlugin.getActivePump().getPumpDescription().storesCarbInfo) { + if (!(recordOnlyCheckbox.isChecked() && (detailedBolusInfo.insulin > 0 || ConfigBuilderPlugin.getActivePump().getPumpDescription().storesCarbInfo))) { ConfigBuilderPlugin.getCommandQueue().bolus(detailedBolusInfo, new Callback() { @Override public void run() { @@ -167,23 +180,21 @@ public class NewTreatmentDialog extends DialogFragment implements OnClickListene Intent i = new Intent(MainApp.instance(), ErrorHelperActivity.class); i.putExtra("soundid", R.raw.boluserror); i.putExtra("status", result.comment); - i.putExtra("title", MainApp.sResources.getString(R.string.treatmentdeliveryerror)); + i.putExtra("title", MainApp.gs(R.string.treatmentdeliveryerror)); i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); MainApp.instance().startActivity(i); } } }); } else { - MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); + TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo); } - Answers.getInstance().logCustom(new CustomEvent("Bolus")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("Bolus")); } } } }); - builder.setNegativeButton( - - getString(R.string.cancel), null); + builder.setNegativeButton(MainApp.gs(R.string.cancel), null); builder.show(); dismiss(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/WizardDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/WizardDialog.java index d74c9caadf..e15c34bac8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/WizardDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/WizardDialog.java @@ -21,11 +21,11 @@ import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; +import android.widget.EditText; import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import com.squareup.otto.Subscribe; @@ -50,19 +50,22 @@ import info.nightscout.androidaps.db.CareportalEvent; import info.nightscout.androidaps.db.DatabaseHelper; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.db.TempTarget; +import info.nightscout.androidaps.events.EventFeatureRunning; import info.nightscout.androidaps.events.EventNewBG; import info.nightscout.androidaps.events.EventRefreshOverview; +import info.nightscout.androidaps.interfaces.Constraint; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensData; +import info.nightscout.androidaps.plugins.IobCobCalculator.CobInfo; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventAutosensCalculationFinished; import info.nightscout.androidaps.plugins.Loop.LoopPlugin; -import info.nightscout.androidaps.plugins.OpenAPSAMA.OpenAPSAMAPlugin; -import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateGui; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.androidaps.queue.Callback; import info.nightscout.utils.BolusWizard; import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.NumberPicker; import info.nightscout.utils.SP; import info.nightscout.utils.SafeParse; @@ -102,6 +105,9 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com NumberPicker editCorr; NumberPicker editCarbTime; + LinearLayout notesLayout; + EditText notesEdit; + Integer calculatedCarbs = 0; Double calculatedTotalInsulin = 0d; JSONObject boluscalcJSON; @@ -132,6 +138,7 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com public void onResume() { super.onResume(); MainApp.bus().register(this); + MainApp.bus().post(new EventFeatureRunning(EventFeatureRunning.Feature.WIZARD)); } @Override @@ -203,6 +210,10 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com superbolus = (TextView) view.findViewById(R.id.treatments_wizard_sb); superbolusInsulin = (TextView) view.findViewById(R.id.treatments_wizard_sbinsulin); + notesLayout = view.findViewById(R.id.treatments_wizard_notes_layout); + notesLayout.setVisibility(SP.getBoolean(R.string.key_show_notes_entry_dialogs, false) ? View.VISIBLE : View.GONE); + notesEdit = (EditText) view.findViewById(R.id.treatment_wizard_notes); + bgTrend = (TextView) view.findViewById(R.id.treatments_wizard_bgtrend); bgTrendInsulin = (TextView) view.findViewById(R.id.treatments_wizard_bgtrendinsulin); cobLayout = (LinearLayout) view.findViewById(R.id.treatments_wizard_cob_layout); @@ -236,13 +247,13 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com superbolusCheckbox.setVisibility(SP.getBoolean(R.string.key_usesuperbolus, false) ? View.VISIBLE : View.GONE); - Integer maxCarbs = MainApp.getConfigBuilder().applyCarbsConstraints(Constants.carbsOnlyForCheckLimit); - Double maxCorrection = MainApp.getConfigBuilder().applyBolusConstraints(Constants.bolusOnlyForCheckLimit); + Integer maxCarbs = MainApp.getConstraintChecker().getMaxCarbsAllowed().value(); + Double maxCorrection = MainApp.getConstraintChecker().getMaxBolusAllowed().value(); editBg.setParams(0d, 0d, 500d, 0.1d, new DecimalFormat("0.0"), false, textWatcher); editCarbs.setParams(0d, 0d, (double) maxCarbs, 1d, new DecimalFormat("0"), false, textWatcher); double bolusstep = ConfigBuilderPlugin.getActivePump().getPumpDescription().bolusStep; - editCorr.setParams(0d, -maxCorrection, maxCorrection, bolusstep, new DecimalFormat("0.00"), false, textWatcher); + editCorr.setParams(0d, -maxCorrection, maxCorrection, bolusstep, DecimalFormatter.pumpSupportedBolusFormat(), false, textWatcher); editCarbTime.setParams(0d, -60d, 60d, 5d, new DecimalFormat("0"), false); initDialog(); @@ -254,24 +265,18 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { saveCheckedStates(); - ttCheckbox.setEnabled(bgCheckbox.isChecked() && MainApp.getConfigBuilder().getTempTargetFromHistory() != null); + ttCheckbox.setEnabled(bgCheckbox.isChecked() && TreatmentsPlugin.getPlugin().getTempTargetFromHistory() != null); calculateInsulin(); } private void saveCheckedStates() { - //SP.putBoolean(getString(R.string.key_wizard_include_bg), bgCheckbox.isChecked()); SP.putBoolean(getString(R.string.key_wizard_include_cob), cobCheckbox.isChecked()); SP.putBoolean(getString(R.string.key_wizard_include_trend_bg), bgtrendCheckbox.isChecked()); - //SP.putBoolean(getString(R.string.key_wizard_include_bolus_iob), bolusIobCheckbox.isChecked()); - //SP.putBoolean(getString(R.string.key_wizard_include_basal_iob), basalIobCheckbox.isChecked()); } private void loadCheckedStates() { - //bgCheckbox.setChecked(SP.getBoolean(getString(R.string.key_wizard_include_bg), true)); bgtrendCheckbox.setChecked(SP.getBoolean(getString(R.string.key_wizard_include_trend_bg), false)); cobCheckbox.setChecked(SP.getBoolean(getString(R.string.key_wizard_include_cob), false)); - //bolusIobCheckbox.setChecked(SP.getBoolean(getString(R.string.key_wizard_include_bolus_iob), true)); - //basalIobCheckbox.setChecked(SP.getBoolean(getString(R.string.key_wizard_include_basal_iob), true)); } @Override @@ -296,13 +301,15 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com return; } okClicked = true; - if (calculatedTotalInsulin > 0d || calculatedCarbs > 0d) { + final Profile profile = MainApp.getConfigBuilder().getProfile(); + + if (profile != null && (calculatedTotalInsulin > 0d || calculatedCarbs > 0d)) { DecimalFormat formatNumber2decimalplaces = new DecimalFormat("0.00"); String confirmMessage = getString(R.string.entertreatmentquestion); - Double insulinAfterConstraints = MainApp.getConfigBuilder().applyBolusConstraints(calculatedTotalInsulin); - Integer carbsAfterConstraints = MainApp.getConfigBuilder().applyCarbsConstraints(calculatedCarbs); + Double insulinAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(calculatedTotalInsulin)).value(); + Integer carbsAfterConstraints = MainApp.getConstraintChecker().applyCarbsConstraints(new Constraint<>(calculatedCarbs)).value(); confirmMessage += "
" + getString(R.string.bolus) + ": " + "" + formatNumber2decimalplaces.format(insulinAfterConstraints) + "U" + ""; confirmMessage += "
" + getString(R.string.carbs) + ": " + carbsAfterConstraints + "g"; @@ -322,6 +329,7 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com final Double bg = SafeParse.stringToDouble(editBg.getText()); final int carbTime = SafeParse.stringToInt(editCarbTime.getText()); final boolean useSuperBolus = superbolusCheckbox.isChecked(); + final String finalNotes = notesEdit.getText().toString(); final AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(MainApp.sResources.getString(R.string.confirmation)); @@ -336,12 +344,12 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com accepted = true; if (finalInsulinAfterConstraints > 0 || finalCarbsAfterConstraints > 0) { if (useSuperBolus) { - final LoopPlugin activeloop = ConfigBuilderPlugin.getActiveLoop(); - if (activeloop != null) { - activeloop.superBolusTo(System.currentTimeMillis() + 2 * 60L * 60 * 1000); + final LoopPlugin loopPlugin = LoopPlugin.getPlugin(); + if (loopPlugin.isEnabled(PluginType.LOOP)) { + loopPlugin.superBolusTo(System.currentTimeMillis() + 2 * 60L * 60 * 1000); MainApp.bus().post(new EventRefreshOverview("WizardDialog")); } - ConfigBuilderPlugin.getCommandQueue().tempBasalPercent(0, 120, true, new Callback() { + ConfigBuilderPlugin.getCommandQueue().tempBasalPercent(0, 120, true, profile, new Callback() { @Override public void run() { if (!result.success) { @@ -365,6 +373,7 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com detailedBolusInfo.carbTime = carbTime; detailedBolusInfo.boluscalc = boluscalcJSON; detailedBolusInfo.source = Source.USER; + detailedBolusInfo.notes = finalNotes; if (detailedBolusInfo.insulin > 0 || ConfigBuilderPlugin.getActivePump().getPumpDescription().storesCarbInfo) { ConfigBuilderPlugin.getCommandQueue().bolus(detailedBolusInfo, new Callback() { @Override @@ -380,9 +389,9 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com } }); } else { - MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); + TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo); } - Answers.getInstance().logCustom(new CustomEvent("Wizard")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("Wizard")); } } } @@ -400,10 +409,11 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com private void initDialog() { Profile profile = MainApp.getConfigBuilder().getProfile(); - ProfileStore profileStore = ConfigBuilderPlugin.getActiveProfileInterface().getProfile(); + ProfileStore profileStore = MainApp.getConfigBuilder().getActiveProfileInterface().getProfile(); if (profile == null) { ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.noprofile)); + dismiss(); return; } @@ -428,13 +438,13 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com } else { editBg.setValue(0d); } - ttCheckbox.setEnabled(MainApp.getConfigBuilder().getTempTargetFromHistory() != null); + ttCheckbox.setEnabled(TreatmentsPlugin.getPlugin().getTempTargetFromHistory() != null); // IOB calculation - MainApp.getConfigBuilder().updateTotalIOBTreatments(); - IobTotal bolusIob = MainApp.getConfigBuilder().getLastCalculationTreatments().round(); - MainApp.getConfigBuilder().updateTotalIOBTempBasals(); - IobTotal basalIob = MainApp.getConfigBuilder().getLastCalculationTempBasals().round(); + TreatmentsPlugin.getPlugin().updateTotalIOBTreatments(); + IobTotal bolusIob = TreatmentsPlugin.getPlugin().getLastCalculationTreatments().round(); + TreatmentsPlugin.getPlugin().updateTotalIOBTempBasals(); + IobTotal basalIob = TreatmentsPlugin.getPlugin().getLastCalculationTempBasals().round(); bolusIobInsulin.setText(DecimalFormatter.to2Decimal(-bolusIob.iob) + "U"); basalIobInsulin.setText(DecimalFormatter.to2Decimal(-basalIob.basaliob) + "U"); @@ -443,27 +453,29 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com } private void calculateInsulin() { - ProfileStore profile = ConfigBuilderPlugin.getActiveProfileInterface().getProfile(); - if (profileSpinner == null || profileSpinner.getSelectedItem() == null) + ProfileStore profileStore = MainApp.getConfigBuilder().getActiveProfileInterface().getProfile(); + if (profileSpinner == null || profileSpinner.getSelectedItem() == null || profileStore == null) return; // not initialized yet String selectedAlternativeProfile = profileSpinner.getSelectedItem().toString(); Profile specificProfile; if (selectedAlternativeProfile.equals(MainApp.sResources.getString(R.string.active))) specificProfile = MainApp.getConfigBuilder().getProfile(); else - specificProfile = profile.getSpecificProfile(selectedAlternativeProfile); + specificProfile = profileStore.getSpecificProfile(selectedAlternativeProfile); // Entered values Double c_bg = SafeParse.stringToDouble(editBg.getText()); Integer c_carbs = SafeParse.stringToInt(editCarbs.getText()); Double c_correction = SafeParse.stringToDouble(editCorr.getText()); - Double corrAfterConstraint = MainApp.getConfigBuilder().applyBolusConstraints(c_correction); + Double corrAfterConstraint = c_correction; + if (c_correction > 0) + c_correction = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(c_correction)).value(); if (c_correction - corrAfterConstraint != 0) { // c_correction != corrAfterConstraint doesn't work editCorr.setValue(0d); ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), getString(R.string.bolusconstraintapplied)); return; } - Integer carbsAfterConstraint = MainApp.getConfigBuilder().applyCarbsConstraints(c_carbs); + Integer carbsAfterConstraint = MainApp.getConstraintChecker().applyCarbsConstraints(new Constraint<>(c_carbs)).value(); if (c_carbs - carbsAfterConstraint != 0) { editCarbs.setValue(0d); ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), getString(R.string.carbsconstraintapplied)); @@ -471,16 +483,14 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com } c_bg = bgCheckbox.isChecked() ? c_bg : 0d; - TempTarget tempTarget = ttCheckbox.isChecked() ? MainApp.getConfigBuilder().getTempTargetFromHistory() : null; + TempTarget tempTarget = ttCheckbox.isChecked() ? TreatmentsPlugin.getPlugin().getTempTargetFromHistory() : null; // COB Double c_cob = 0d; if (cobCheckbox.isChecked()) { - AutosensData autosensData = IobCobCalculatorPlugin.getLastAutosensData("Wizard COB"); - - if(autosensData != null) { - c_cob = autosensData.cob; - } + CobInfo cobInfo = IobCobCalculatorPlugin.getPlugin().getCobInfo(false, "Wizard COB"); + if (cobInfo != null && cobInfo.displayCob != null) + c_cob = cobInfo.displayCob; } BolusWizard wizard = new BolusWizard(); @@ -530,7 +540,7 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com } if (calculatedTotalInsulin > 0d || calculatedCarbs > 0d) { - String insulinText = calculatedTotalInsulin > 0d ? (DecimalFormatter.to2Decimal(calculatedTotalInsulin) + "U") : ""; + String insulinText = calculatedTotalInsulin > 0d ? (DecimalFormatter.toPumpSupportedBolus(calculatedTotalInsulin) + "U") : ""; String carbsText = calculatedCarbs > 0d ? (DecimalFormatter.to0Decimal(calculatedCarbs) + "g") : ""; total.setText(MainApp.gs(R.string.result) + ": " + insulinText + " " + carbsText); okButton.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewFragment.java index 46e454347d..2536e8bf5c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewFragment.java @@ -3,9 +3,10 @@ package info.nightscout.androidaps.plugins.Overview; import android.annotation.SuppressLint; import android.app.Activity; import android.app.NotificationManager; +import android.content.ActivityNotFoundException; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.PackageManager; import android.graphics.Color; import android.graphics.Paint; import android.os.Bundle; @@ -13,27 +14,28 @@ import android.os.Handler; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; -import android.support.v4.content.ContextCompat; +import android.support.v4.content.res.ResourcesCompat; import android.support.v7.app.AlertDialog; -import android.support.v7.widget.CardView; import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.PopupMenu; import android.support.v7.widget.RecyclerView; +import android.text.SpannableString; +import android.text.style.ForegroundColorSpan; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.ContextMenu; import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; +import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; +import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import com.jjoe64.graphview.GraphView; import com.squareup.otto.Subscribe; @@ -46,8 +48,6 @@ import org.slf4j.LoggerFactory; import java.text.DecimalFormat; import java.util.Calendar; import java.util.Date; -import java.util.List; -import java.util.Objects; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -73,12 +73,14 @@ import info.nightscout.androidaps.events.EventCareportalEventChange; import info.nightscout.androidaps.events.EventExtendedBolusChange; import info.nightscout.androidaps.events.EventInitializationChanged; import info.nightscout.androidaps.events.EventPreferenceChange; +import info.nightscout.androidaps.events.EventProfileSwitchChange; import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.events.EventRefreshOverview; import info.nightscout.androidaps.events.EventTempBasalChange; import info.nightscout.androidaps.events.EventTempTargetChange; import info.nightscout.androidaps.events.EventTreatmentChange; -import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.Constraint; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.PumpDescription; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.Careportal.CareportalFragment; @@ -86,31 +88,33 @@ import info.nightscout.androidaps.plugins.Careportal.Dialogs.NewNSTreatmentDialo import info.nightscout.androidaps.plugins.Careportal.OptionsToShow; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.ConstraintsObjectives.ObjectivesPlugin; -import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensData; +import info.nightscout.androidaps.plugins.IobCobCalculator.CobInfo; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventAutosensCalculationFinished; +import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventIobCalculationProgress; import info.nightscout.androidaps.plugins.Loop.LoopPlugin; import info.nightscout.androidaps.plugins.Loop.events.EventNewOpenLoopNotification; -import info.nightscout.androidaps.plugins.NSClientInternal.broadcasts.BroadcastAckAlarm; import info.nightscout.androidaps.plugins.NSClientInternal.data.NSDeviceStatus; -import info.nightscout.androidaps.plugins.OpenAPSAMA.DetermineBasalResultAMA; -import info.nightscout.androidaps.plugins.OpenAPSAMA.OpenAPSAMAPlugin; import info.nightscout.androidaps.plugins.Overview.Dialogs.CalibrationDialog; import info.nightscout.androidaps.plugins.Overview.Dialogs.ErrorHelperActivity; +import info.nightscout.androidaps.plugins.Overview.Dialogs.NewCarbsDialog; +import info.nightscout.androidaps.plugins.Overview.Dialogs.NewInsulinDialog; import info.nightscout.androidaps.plugins.Overview.Dialogs.NewTreatmentDialog; import info.nightscout.androidaps.plugins.Overview.Dialogs.WizardDialog; import info.nightscout.androidaps.plugins.Overview.activities.QuickWizardListActivity; -import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.Overview.events.EventSetWakeLock; import info.nightscout.androidaps.plugins.Overview.graphData.GraphData; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; +import info.nightscout.androidaps.plugins.Overview.notifications.NotificationRecyclerViewAdapter; import info.nightscout.androidaps.plugins.Overview.notifications.NotificationStore; -import info.nightscout.androidaps.plugins.SourceXdrip.SourceXdripPlugin; +import info.nightscout.androidaps.plugins.Source.SourceDexcomG5Plugin; +import info.nightscout.androidaps.plugins.Source.SourceXdripPlugin; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.androidaps.plugins.Treatments.fragments.ProfileViewerDialog; import info.nightscout.androidaps.queue.Callback; import info.nightscout.utils.BolusWizard; import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.NSUpload; import info.nightscout.utils.OKDialog; import info.nightscout.utils.Profiler; @@ -118,7 +122,7 @@ import info.nightscout.utils.SP; import info.nightscout.utils.SingleClickButton; import info.nightscout.utils.ToastUtils; -public class OverviewFragment extends Fragment implements View.OnClickListener, CompoundButton.OnCheckedChangeListener, View.OnLongClickListener { +public class OverviewFragment extends Fragment implements View.OnClickListener, View.OnLongClickListener { private static Logger log = LoggerFactory.getLogger(OverviewFragment.class); TextView timeView; @@ -138,37 +142,30 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, TextView pumpDeviceStatusView; TextView openapsDeviceStatusView; TextView uploaderDeviceStatusView; + TextView iobCalculationProgressView; LinearLayout loopStatusLayout; LinearLayout pumpStatusLayout; GraphView bgGraph; GraphView iobGraph; + ImageButton chartButton; TextView iage; TextView cage; TextView sage; TextView pbage; - TextView showPredictionLabel; - CheckBox showPredictionCheckbox; - TextView showBasalsLabel; - CheckBox showBasalsCheckbox; - TextView showIobLabel; - CheckBox showIobCheckbox; - TextView showCobLabel; - CheckBox showCobCheckbox; - TextView showDeviationsLabel; - CheckBox showDeviationsCheckbox; - TextView showRatiosLabel; - CheckBox showRatiosCheckbox; - RecyclerView notificationsView; LinearLayoutManager llm; LinearLayout acceptTempLayout; + SingleClickButton acceptTempButton; + SingleClickButton treatmentButton; SingleClickButton wizardButton; SingleClickButton calibrationButton; - SingleClickButton acceptTempButton; + SingleClickButton insulinButton; + SingleClickButton carbsButton; + SingleClickButton cgmButton; SingleClickButton quickWizardButton; CheckBox lockScreen; @@ -187,6 +184,8 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, final Object updateSync = new Object(); + public enum CHARTTYPE {PRE, BAS, IOB, COB, DEV, SEN, DEVSLOPE} + private static final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor(); private static ScheduledFuture scheduledUpdate = null; @@ -237,6 +236,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, pumpDeviceStatusView = (TextView) view.findViewById(R.id.overview_pump); openapsDeviceStatusView = (TextView) view.findViewById(R.id.overview_openaps); uploaderDeviceStatusView = (TextView) view.findViewById(R.id.overview_uploader); + iobCalculationProgressView = (TextView) view.findViewById(R.id.overview_iobcalculationprogess); loopStatusLayout = (LinearLayout) view.findViewById(R.id.overview_looplayout); pumpStatusLayout = (LinearLayout) view.findViewById(R.id.overview_pumpstatuslayout); @@ -259,6 +259,12 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, treatmentButton.setOnClickListener(this); wizardButton = (SingleClickButton) view.findViewById(R.id.overview_wizardbutton); wizardButton.setOnClickListener(this); + insulinButton = (SingleClickButton) view.findViewById(R.id.overview_insulinbutton); + if (insulinButton != null) + insulinButton.setOnClickListener(this); + carbsButton = (SingleClickButton) view.findViewById(R.id.overview_carbsbutton); + if (carbsButton != null) + carbsButton.setOnClickListener(this); acceptTempButton = (SingleClickButton) view.findViewById(R.id.overview_accepttempbutton); if (acceptTempButton != null) acceptTempButton.setOnClickListener(this); @@ -268,53 +274,39 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, calibrationButton = (SingleClickButton) view.findViewById(R.id.overview_calibrationbutton); if (calibrationButton != null) calibrationButton.setOnClickListener(this); + cgmButton = (SingleClickButton) view.findViewById(R.id.overview_cgmbutton); + if (cgmButton != null) + cgmButton.setOnClickListener(this); acceptTempLayout = (LinearLayout) view.findViewById(R.id.overview_accepttemplayout); - showPredictionCheckbox = (CheckBox) view.findViewById(R.id.overview_showprediction); - showBasalsCheckbox = (CheckBox) view.findViewById(R.id.overview_showbasals); - showIobCheckbox = (CheckBox) view.findViewById(R.id.overview_showiob); - showCobCheckbox = (CheckBox) view.findViewById(R.id.overview_showcob); - showDeviationsCheckbox = (CheckBox) view.findViewById(R.id.overview_showdeviations); - showRatiosCheckbox = (CheckBox) view.findViewById(R.id.overview_showratios); - showPredictionCheckbox.setChecked(SP.getBoolean("showprediction", false)); - showBasalsCheckbox.setChecked(SP.getBoolean("showbasals", true)); - showIobCheckbox.setChecked(SP.getBoolean("showiob", false)); - showCobCheckbox.setChecked(SP.getBoolean("showcob", false)); - showDeviationsCheckbox.setChecked(SP.getBoolean("showdeviations", false)); - showRatiosCheckbox.setChecked(SP.getBoolean("showratios", false)); - showPredictionCheckbox.setOnCheckedChangeListener(this); - showBasalsCheckbox.setOnCheckedChangeListener(this); - showIobCheckbox.setOnCheckedChangeListener(this); - showCobCheckbox.setOnCheckedChangeListener(this); - showDeviationsCheckbox.setOnCheckedChangeListener(this); - showRatiosCheckbox.setOnCheckedChangeListener(this); - - showPredictionLabel = (TextView) view.findViewById(R.id.overview_showprediction_label); - showPredictionLabel.setOnClickListener(this); - showBasalsLabel = (TextView) view.findViewById(R.id.overview_showbasals_label); - showBasalsLabel.setOnClickListener(this); - showIobLabel = (TextView) view.findViewById(R.id.overview_showiob_label); - showIobLabel.setOnClickListener(this); - showCobLabel = (TextView) view.findViewById(R.id.overview_showcob_label); - showCobLabel.setOnClickListener(this); - showDeviationsLabel = (TextView) view.findViewById(R.id.overview_showdeviations_label); - showDeviationsLabel.setOnClickListener(this); - showRatiosLabel = (TextView) view.findViewById(R.id.overview_showratios_label); - showRatiosLabel.setOnClickListener(this); - notificationsView = (RecyclerView) view.findViewById(R.id.overview_notifications); notificationsView.setHasFixedSize(true); llm = new LinearLayoutManager(view.getContext()); notificationsView.setLayoutManager(llm); + int axisWidth = 50; + + if (dm.densityDpi <= 120) + axisWidth = 3; + else if (dm.densityDpi <= 160) + axisWidth = 10; + else if (dm.densityDpi <= 320) + axisWidth = 35; + else if (dm.densityDpi <= 420) + axisWidth = 50; + else if (dm.densityDpi <= 560) + axisWidth = 70; + else + axisWidth = 80; + bgGraph.getGridLabelRenderer().setGridColor(MainApp.sResources.getColor(R.color.graphgrid)); bgGraph.getGridLabelRenderer().reloadStyles(); iobGraph.getGridLabelRenderer().setGridColor(MainApp.sResources.getColor(R.color.graphgrid)); iobGraph.getGridLabelRenderer().reloadStyles(); iobGraph.getGridLabelRenderer().setHorizontalLabelsVisible(false); - bgGraph.getGridLabelRenderer().setLabelVerticalWidth(50); - iobGraph.getGridLabelRenderer().setLabelVerticalWidth(50); + bgGraph.getGridLabelRenderer().setLabelVerticalWidth(axisWidth); + iobGraph.getGridLabelRenderer().setLabelVerticalWidth(axisWidth); iobGraph.getGridLabelRenderer().setNumVerticalLabels(5); rangeToDisplay = SP.getInt(R.string.key_rangetodisplay, 6); @@ -330,6 +322,8 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, } }); + setupChartMenu(view); + lockScreen = (CheckBox) view.findViewById(R.id.overview_lockscreen); if (lockScreen != null) { lockScreen.setChecked(SP.getBoolean("lockscreen", false)); @@ -344,266 +338,243 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, return view; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); log.debug("Runtime Exception", e); } return null; } + private void setupChartMenu(View view) { + chartButton = (ImageButton) view.findViewById(R.id.overview_chartMenuButton); + chartButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + final LoopPlugin.LastRun finalLastRun = LoopPlugin.lastRun; + final boolean predictionsAvailable = finalLastRun != null && finalLastRun.request.hasPredictions; + + MenuItem item; + CharSequence title; + SpannableString s; + PopupMenu popup = new PopupMenu(v.getContext(), v); + + if (predictionsAvailable) { + item = popup.getMenu().add(Menu.NONE, CHARTTYPE.PRE.ordinal(), Menu.NONE, "Predictions"); + title = item.getTitle(); + s = new SpannableString(title); + s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.prediction, null)), 0, s.length(), 0); + item.setTitle(s); + item.setCheckable(true); + item.setChecked(SP.getBoolean("showprediction", true)); + } + + item = popup.getMenu().add(Menu.NONE, CHARTTYPE.BAS.ordinal(), Menu.NONE, MainApp.gs(R.string.overview_show_basals)); + title = item.getTitle(); + s = new SpannableString(title); + s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.basal, null)), 0, s.length(), 0); + item.setTitle(s); + item.setCheckable(true); + item.setChecked(SP.getBoolean("showbasals", true)); + + item = popup.getMenu().add(Menu.NONE, CHARTTYPE.IOB.ordinal(), Menu.NONE, MainApp.gs(R.string.overview_show_iob)); + title = item.getTitle(); + s = new SpannableString(title); + s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.iob, null)), 0, s.length(), 0); + item.setTitle(s); + item.setCheckable(true); + item.setChecked(SP.getBoolean("showiob", true)); + + item = popup.getMenu().add(Menu.NONE, CHARTTYPE.COB.ordinal(), Menu.NONE, MainApp.gs(R.string.overview_show_cob)); + title = item.getTitle(); + s = new SpannableString(title); + s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.cob, null)), 0, s.length(), 0); + item.setTitle(s); + item.setCheckable(true); + item.setChecked(SP.getBoolean("showcob", true)); + + item = popup.getMenu().add(Menu.NONE, CHARTTYPE.DEV.ordinal(), Menu.NONE, MainApp.gs(R.string.overview_show_deviations)); + title = item.getTitle(); + s = new SpannableString(title); + s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.deviations, null)), 0, s.length(), 0); + item.setTitle(s); + item.setCheckable(true); + item.setChecked(SP.getBoolean("showdeviations", false)); + + item = popup.getMenu().add(Menu.NONE, CHARTTYPE.SEN.ordinal(), Menu.NONE, MainApp.gs(R.string.overview_show_sensitivity)); + title = item.getTitle(); + s = new SpannableString(title); + s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.ratio, null)), 0, s.length(), 0); + item.setTitle(s); + item.setCheckable(true); + item.setChecked(SP.getBoolean("showratios", false)); + + if (MainApp.devBranch) { + item = popup.getMenu().add(Menu.NONE, CHARTTYPE.DEVSLOPE.ordinal(), Menu.NONE, "Deviation slope"); + title = item.getTitle(); + s = new SpannableString(title); + s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.devslopepos, null)), 0, s.length(), 0); + item.setTitle(s); + item.setCheckable(true); + item.setChecked(SP.getBoolean("showdevslope", false)); + } + + popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + if (item.getItemId() == CHARTTYPE.PRE.ordinal()) { + SP.putBoolean("showprediction", !item.isChecked()); + } else if (item.getItemId() == CHARTTYPE.BAS.ordinal()) { + SP.putBoolean("showbasals", !item.isChecked()); + } else if (item.getItemId() == CHARTTYPE.IOB.ordinal()) { + SP.putBoolean("showiob", !item.isChecked()); + } else if (item.getItemId() == CHARTTYPE.COB.ordinal()) { + SP.putBoolean("showcob", !item.isChecked()); + } else if (item.getItemId() == CHARTTYPE.DEV.ordinal()) { + SP.putBoolean("showdeviations", !item.isChecked()); + } else if (item.getItemId() == CHARTTYPE.SEN.ordinal()) { + SP.putBoolean("showratios", !item.isChecked()); + } else if (item.getItemId() == CHARTTYPE.DEVSLOPE.ordinal()) { + SP.putBoolean("showdevslope", !item.isChecked()); + } + scheduleUpdateGUI("onGraphCheckboxesCheckedChanged"); + return true; + } + }); + chartButton.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp); + popup.setOnDismissListener(new PopupMenu.OnDismissListener() { + @Override + public void onDismiss(PopupMenu menu) { + chartButton.setImageResource(R.drawable.ic_arrow_drop_down_white_24dp); + } + }); + popup.show(); + } + }); + } + @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); if (v == apsModeView) { - final LoopPlugin activeloop = ConfigBuilderPlugin.getActiveLoop(); + final LoopPlugin loopPlugin = LoopPlugin.getPlugin(); final PumpDescription pumpDescription = ConfigBuilderPlugin.getActivePump().getPumpDescription(); - if (activeloop == null) + if (loopPlugin == null || !MainApp.getConfigBuilder().isProfileValid("ContexMenuCreation")) return; - menu.setHeaderTitle(MainApp.sResources.getString(R.string.loop)); - if (activeloop.isEnabled(PluginBase.LOOP)) { - menu.add(MainApp.sResources.getString(R.string.disableloop)); - if (!activeloop.isSuspended()) { - menu.add(MainApp.sResources.getString(R.string.suspendloopfor1h)); - menu.add(MainApp.sResources.getString(R.string.suspendloopfor2h)); - menu.add(MainApp.sResources.getString(R.string.suspendloopfor3h)); - menu.add(MainApp.sResources.getString(R.string.suspendloopfor10h)); - if (pumpDescription.tempDurationStep <= 30) - menu.add(MainApp.sResources.getString(R.string.disconnectpumpfor30m)); - menu.add(MainApp.sResources.getString(R.string.disconnectpumpfor1h)); - menu.add(MainApp.sResources.getString(R.string.disconnectpumpfor2h)); - menu.add(MainApp.sResources.getString(R.string.disconnectpumpfor3h)); + menu.setHeaderTitle(MainApp.gs(R.string.loop)); + if (loopPlugin.isEnabled(PluginType.LOOP)) { + menu.add(MainApp.gs(R.string.disableloop)); + if (!loopPlugin.isSuspended()) { + menu.add(MainApp.gs(R.string.suspendloopfor1h)); + menu.add(MainApp.gs(R.string.suspendloopfor2h)); + menu.add(MainApp.gs(R.string.suspendloopfor3h)); + menu.add(MainApp.gs(R.string.suspendloopfor10h)); + if (pumpDescription.tempDurationStep15mAllowed) + menu.add(MainApp.gs(R.string.disconnectpumpfor15m)); + if (pumpDescription.tempDurationStep30mAllowed) + menu.add(MainApp.gs(R.string.disconnectpumpfor30m)); + menu.add(MainApp.gs(R.string.disconnectpumpfor1h)); + menu.add(MainApp.gs(R.string.disconnectpumpfor2h)); + menu.add(MainApp.gs(R.string.disconnectpumpfor3h)); } else { - menu.add(MainApp.sResources.getString(R.string.resume)); + menu.add(MainApp.gs(R.string.resume)); } } - if (!activeloop.isEnabled(PluginBase.LOOP)) - menu.add(MainApp.sResources.getString(R.string.enableloop)); + if (!loopPlugin.isEnabled(PluginType.LOOP)) + menu.add(MainApp.gs(R.string.enableloop)); } else if (v == activeProfileView) { - menu.setHeaderTitle(MainApp.sResources.getString(R.string.profile)); - menu.add(MainApp.sResources.getString(R.string.danar_viewprofile)); - menu.add(MainApp.sResources.getString(R.string.careportal_profileswitch)); + menu.setHeaderTitle(MainApp.gs(R.string.profile)); + menu.add(MainApp.gs(R.string.danar_viewprofile)); + if (MainApp.getConfigBuilder().getActiveProfileInterface().getProfile() != null) { + menu.add(MainApp.gs(R.string.careportal_profileswitch)); + } } } - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - switch (buttonView.getId()) { - case R.id.overview_showprediction: - case R.id.overview_showbasals: - case R.id.overview_showiob: - break; - case R.id.overview_showcob: - showDeviationsCheckbox.setOnCheckedChangeListener(null); - showDeviationsCheckbox.setChecked(false); - showDeviationsCheckbox.setOnCheckedChangeListener(this); - break; - case R.id.overview_showdeviations: - showCobCheckbox.setOnCheckedChangeListener(null); - showCobCheckbox.setChecked(false); - showCobCheckbox.setOnCheckedChangeListener(this); - break; - case R.id.overview_showratios: - break; - } - SP.putBoolean("showiob", showIobCheckbox.isChecked()); - SP.putBoolean("showprediction", showPredictionCheckbox.isChecked()); - SP.putBoolean("showbasals", showBasalsCheckbox.isChecked()); - SP.putBoolean("showcob", showCobCheckbox.isChecked()); - SP.putBoolean("showdeviations", showDeviationsCheckbox.isChecked()); - SP.putBoolean("showratios", showRatiosCheckbox.isChecked()); - scheduleUpdateGUI("onGraphCheckboxesCheckedChanged"); - } - @Override public boolean onContextItemSelected(MenuItem item) { - final LoopPlugin activeloop = ConfigBuilderPlugin.getActiveLoop(); - if (item.getTitle().equals(MainApp.sResources.getString(R.string.disableloop))) { - activeloop.setFragmentEnabled(PluginBase.LOOP, false); - activeloop.setFragmentVisible(PluginBase.LOOP, false); - MainApp.getConfigBuilder().storeSettings(); + final Profile profile = MainApp.getConfigBuilder().getProfile(); + if (profile == null) + return true; + final LoopPlugin loopPlugin = LoopPlugin.getPlugin(); + if (item.getTitle().equals(MainApp.gs(R.string.disableloop))) { + loopPlugin.setPluginEnabled(PluginType.LOOP, false); + loopPlugin.setFragmentVisible(PluginType.LOOP, false); + MainApp.getConfigBuilder().storeSettings("DisablingLoop"); updateGUI("suspendmenu"); ConfigBuilderPlugin.getCommandQueue().cancelTempBasal(true, new Callback() { @Override public void run() { if (!result.success) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.tempbasaldeliveryerror)); + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.tempbasaldeliveryerror)); } } }); - NSUpload.uploadOpenAPSOffline(60); // upload 60 min, we don;t know real duration + NSUpload.uploadOpenAPSOffline(24 * 60); // upload 24h, we don't know real duration return true; - } else if (item.getTitle().equals(MainApp.sResources.getString(R.string.enableloop))) { - activeloop.setFragmentEnabled(PluginBase.LOOP, true); - activeloop.setFragmentVisible(PluginBase.LOOP, true); - MainApp.getConfigBuilder().storeSettings(); + } else if (item.getTitle().equals(MainApp.gs(R.string.enableloop))) { + loopPlugin.setPluginEnabled(PluginType.LOOP, true); + loopPlugin.setFragmentVisible(PluginType.LOOP, true); + MainApp.getConfigBuilder().storeSettings("EnablingLoop"); updateGUI("suspendmenu"); NSUpload.uploadOpenAPSOffline(0); return true; - } else if (item.getTitle().equals(MainApp.sResources.getString(R.string.resume))) { - activeloop.suspendTo(0L); + } else if (item.getTitle().equals(MainApp.gs(R.string.resume))) { + loopPlugin.suspendTo(0L); updateGUI("suspendmenu"); ConfigBuilderPlugin.getCommandQueue().cancelTempBasal(true, new Callback() { @Override public void run() { if (!result.success) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.tempbasaldeliveryerror)); + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.tempbasaldeliveryerror)); } } }); NSUpload.uploadOpenAPSOffline(0); return true; - } else if (item.getTitle().equals(MainApp.sResources.getString(R.string.suspendloopfor1h))) { - activeloop.suspendTo(System.currentTimeMillis() + 60L * 60 * 1000); + } else if (item.getTitle().equals(MainApp.gs(R.string.suspendloopfor1h))) { + MainApp.getConfigBuilder().suspendLoop(60); updateGUI("suspendmenu"); - ConfigBuilderPlugin.getCommandQueue().cancelTempBasal(true, new Callback() { - @Override - public void run() { - if (!result.success) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.tempbasaldeliveryerror)); - } - } - }); - NSUpload.uploadOpenAPSOffline(60); return true; - } else if (item.getTitle().equals(MainApp.sResources.getString(R.string.suspendloopfor2h))) { - activeloop.suspendTo(System.currentTimeMillis() + 2 * 60L * 60 * 1000); + } else if (item.getTitle().equals(MainApp.gs(R.string.suspendloopfor2h))) { + MainApp.getConfigBuilder().suspendLoop(120); updateGUI("suspendmenu"); - ConfigBuilderPlugin.getCommandQueue().cancelTempBasal(true, new Callback() { - @Override - public void run() { - if (!result.success) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.tempbasaldeliveryerror)); - } - } - }); - NSUpload.uploadOpenAPSOffline(120); return true; - } else if (item.getTitle().equals(MainApp.sResources.getString(R.string.suspendloopfor3h))) { - activeloop.suspendTo(System.currentTimeMillis() + 3 * 60L * 60 * 1000); + } else if (item.getTitle().equals(MainApp.gs(R.string.suspendloopfor3h))) { + MainApp.getConfigBuilder().suspendLoop(180); updateGUI("suspendmenu"); - ConfigBuilderPlugin.getCommandQueue().cancelTempBasal(true, new Callback() { - @Override - public void run() { - if (!result.success) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.tempbasaldeliveryerror)); - } - } - }); - NSUpload.uploadOpenAPSOffline(180); return true; - } else if (item.getTitle().equals(MainApp.sResources.getString(R.string.suspendloopfor10h))) { - activeloop.suspendTo(System.currentTimeMillis() + 10 * 60L * 60 * 1000); + } else if (item.getTitle().equals(MainApp.gs(R.string.suspendloopfor10h))) { + MainApp.getConfigBuilder().suspendLoop(600); updateGUI("suspendmenu"); - ConfigBuilderPlugin.getCommandQueue().cancelTempBasal(true, new Callback() { - @Override - public void run() { - if (!result.success) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.tempbasaldeliveryerror)); - } - } - }); - NSUpload.uploadOpenAPSOffline(600); return true; - } else if (item.getTitle().equals(MainApp.sResources.getString(R.string.disconnectpumpfor30m))) { - activeloop.disconnectTo(System.currentTimeMillis() + 30L * 60 * 1000); + } else if (item.getTitle().equals(MainApp.gs(R.string.disconnectpumpfor15m))) { + MainApp.getConfigBuilder().disconnectPump(15, profile); updateGUI("suspendmenu"); - ConfigBuilderPlugin.getCommandQueue().tempBasalPercent(0, 30, true, new Callback() { - @Override - public void run() { - if (!result.success) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.tempbasaldeliveryerror)); - } - } - }); - if (MainApp.getConfigBuilder().getActivePump().getPumpDescription().isExtendedBolusCapable && MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) { - ConfigBuilderPlugin.getCommandQueue().cancelExtended( new Callback() { - @Override - public void run() { - if (!result.success) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.extendedbolusdeliveryerror)); - } - } - }); - } - NSUpload.uploadOpenAPSOffline(30); return true; - } else if (item.getTitle().equals(MainApp.sResources.getString(R.string.disconnectpumpfor1h))) { - activeloop.disconnectTo(System.currentTimeMillis() + 1 * 60L * 60 * 1000); + } else if (item.getTitle().equals(MainApp.gs(R.string.disconnectpumpfor30m))) { + MainApp.getConfigBuilder().disconnectPump(30, profile); updateGUI("suspendmenu"); - ConfigBuilderPlugin.getCommandQueue().tempBasalPercent(0, 60, true, new Callback() { - @Override - public void run() { - if (!result.success) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.tempbasaldeliveryerror)); - } - } - }); - if (MainApp.getConfigBuilder().getActivePump().getPumpDescription().isExtendedBolusCapable && MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) { - ConfigBuilderPlugin.getCommandQueue().cancelExtended( new Callback() { - @Override - public void run() { - if (!result.success) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.extendedbolusdeliveryerror)); - } - } - }); - } - NSUpload.uploadOpenAPSOffline(60); return true; - } else if (item.getTitle().equals(MainApp.sResources.getString(R.string.disconnectpumpfor2h))) { - activeloop.disconnectTo(System.currentTimeMillis() + 2 * 60L * 60 * 1000); + } else if (item.getTitle().equals(MainApp.gs(R.string.disconnectpumpfor1h))) { + MainApp.getConfigBuilder().disconnectPump(60, profile); updateGUI("suspendmenu"); - ConfigBuilderPlugin.getCommandQueue().tempBasalPercent(0, 2 * 60, true, new Callback() { - @Override - public void run() { - if (!result.success) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.tempbasaldeliveryerror)); - } - } - }); - if (MainApp.getConfigBuilder().getActivePump().getPumpDescription().isExtendedBolusCapable && MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) { - ConfigBuilderPlugin.getCommandQueue().cancelExtended( new Callback() { - @Override - public void run() { - if (!result.success) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.extendedbolusdeliveryerror)); - } - } - }); - } - NSUpload.uploadOpenAPSOffline(120); return true; - } else if (item.getTitle().equals(MainApp.sResources.getString(R.string.disconnectpumpfor3h))) { - activeloop.disconnectTo(System.currentTimeMillis() + 3 * 60L * 60 * 1000); + } else if (item.getTitle().equals(MainApp.gs(R.string.disconnectpumpfor2h))) { + MainApp.getConfigBuilder().disconnectPump(120, profile); updateGUI("suspendmenu"); - ConfigBuilderPlugin.getCommandQueue().tempBasalPercent(0, 3 * 60, true, new Callback() { - @Override - public void run() { - if (!result.success) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.tempbasaldeliveryerror)); - } - } - }); - if (MainApp.getConfigBuilder().getActivePump().getPumpDescription().isExtendedBolusCapable && MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) { - ConfigBuilderPlugin.getCommandQueue().cancelExtended( new Callback() { - @Override - public void run() { - if (!result.success) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.extendedbolusdeliveryerror)); - } - } - }); - } - NSUpload.uploadOpenAPSOffline(180); return true; - } else if (item.getTitle().equals(MainApp.sResources.getString(R.string.careportal_profileswitch))) { + } else if (item.getTitle().equals(MainApp.gs(R.string.disconnectpumpfor3h))) { + MainApp.getConfigBuilder().disconnectPump(180, profile); + updateGUI("suspendmenu"); + return true; + } else if (item.getTitle().equals(MainApp.gs(R.string.careportal_profileswitch))) { NewNSTreatmentDialog newDialog = new NewNSTreatmentDialog(); final OptionsToShow profileswitch = CareportalFragment.PROFILESWITCHDIRECT; profileswitch.executeProfileSwitch = true; newDialog.setOptions(profileswitch, R.string.careportal_profileswitch); newDialog.show(getFragmentManager(), "NewNSTreatmentDialog"); - } else if (item.getTitle().equals(MainApp.sResources.getString(R.string.danar_viewprofile))) { + } else if (item.getTitle().equals(MainApp.gs(R.string.danar_viewprofile))) { ProfileViewerDialog pvd = ProfileViewerDialog.newInstance(System.currentTimeMillis()); FragmentManager manager = getFragmentManager(); pvd.show(manager, "ProfileViewDialog"); @@ -614,6 +585,10 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, @Override public void onClick(View v) { + boolean xdrip = MainApp.getSpecificPlugin(SourceXdripPlugin.class) != null && MainApp.getSpecificPlugin(SourceXdripPlugin.class).isEnabled(PluginType.BGSOURCE); + boolean g5 = MainApp.getSpecificPlugin(SourceDexcomG5Plugin.class) != null && MainApp.getSpecificPlugin(SourceDexcomG5Plugin.class).isEnabled(PluginType.BGSOURCE); + String units = MainApp.getConfigBuilder().getProfileUnits(); + FragmentManager manager = getFragmentManager(); switch (v.getId()) { case R.id.overview_accepttempbutton: @@ -627,39 +602,63 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, wizardDialog.show(manager, "WizardDialog"); break; case R.id.overview_calibrationbutton: - CalibrationDialog calibrationDialog = new CalibrationDialog(); - calibrationDialog.show(manager, "CalibrationDialog"); + if (xdrip) { + CalibrationDialog calibrationDialog = new CalibrationDialog(); + calibrationDialog.show(manager, "CalibrationDialog"); + } else if (g5) { + try { + Intent i = new Intent("com.dexcom.cgm.activities.MeterEntryActivity"); + startActivity(i); + } catch (ActivityNotFoundException e) { + ToastUtils.showToastInUiThread(getActivity(), MainApp.gs(R.string.g5appnotdetected)); + } + } + break; + case R.id.overview_cgmbutton: + if (xdrip) + openCgmApp("com.eveningoutpost.dexdrip"); + else if (g5 && units.equals(Constants.MGDL)) + openCgmApp("com.dexcom.cgm.region5.mgdl"); + else if (g5 && units.equals(Constants.MMOL)) + openCgmApp("com.dexcom.cgm.region5.mmol"); break; case R.id.overview_treatmentbutton: NewTreatmentDialog treatmentDialogFragment = new NewTreatmentDialog(); treatmentDialogFragment.show(manager, "TreatmentDialog"); break; + case R.id.overview_insulinbutton: + new NewInsulinDialog().show(manager, "InsulinDialog"); + break; + case R.id.overview_carbsbutton: + new NewCarbsDialog().show(manager, "CarbsDialog"); + break; case R.id.overview_pumpstatus: if (ConfigBuilderPlugin.getActivePump().isSuspended() || !ConfigBuilderPlugin.getActivePump().isInitialized()) ConfigBuilderPlugin.getCommandQueue().readStatus("RefreshClicked", null); break; - case R.id.overview_showprediction_label: - showPredictionCheckbox.toggle(); - break; - case R.id.overview_showbasals_label: - showBasalsCheckbox.toggle(); - break; - case R.id.overview_showiob_label: - showIobCheckbox.toggle(); - break; - case R.id.overview_showcob_label: - showCobCheckbox.toggle(); - break; - case R.id.overview_showdeviations_label: - showDeviationsCheckbox.toggle(); - break; - case R.id.overview_showratios_label: - showRatiosCheckbox.toggle(); - break; } } + public boolean openCgmApp(String packageName) { + PackageManager packageManager = getContext().getPackageManager(); + try { + Intent intent = packageManager.getLaunchIntentForPackage(packageName); + if (intent == null) { + throw new ActivityNotFoundException(); + } + intent.addCategory(Intent.CATEGORY_LAUNCHER); + getContext().startActivity(intent); + return true; + } catch (ActivityNotFoundException e) { + new AlertDialog.Builder(getContext()) + .setMessage(R.string.error_starting_cgm) + .setPositiveButton("OK", null) + .show(); + return false; + } + } + @Override public boolean onLongClick(View v) { switch (v.getId()) { @@ -672,36 +671,36 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, } private void onClickAcceptTemp() { - if (ConfigBuilderPlugin.getActiveLoop() != null) { - ConfigBuilderPlugin.getActiveLoop().invoke("Accept temp button", false); + Profile profile = MainApp.getConfigBuilder().getProfile(); + + if (LoopPlugin.getPlugin().isEnabled(PluginType.LOOP) && profile != null) { + LoopPlugin.getPlugin().invoke("Accept temp button", false); final LoopPlugin.LastRun finalLastRun = LoopPlugin.lastRun; - if (finalLastRun != null && finalLastRun.lastAPSRun != null && finalLastRun.constraintsProcessed.changeRequested) { + if (finalLastRun != null && finalLastRun.lastAPSRun != null && finalLastRun.constraintsProcessed.isChangeRequested()) { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); builder.setTitle(getContext().getString(R.string.confirmation)); builder.setMessage(getContext().getString(R.string.setbasalquestion) + "\n" + finalLastRun.constraintsProcessed); - builder.setPositiveButton(getContext().getString(R.string.ok), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - hideTempRecommendation(); - clearNotification(); - MainApp.getConfigBuilder().applyAPSRequest(finalLastRun.constraintsProcessed, new Callback() { - @Override - public void run() { - if (result.enacted) { - finalLastRun.setByPump = result; - finalLastRun.lastEnact = new Date(); - finalLastRun.lastOpenModeAccept = new Date(); - NSUpload.uploadDeviceStatus(); - ObjectivesPlugin objectivesPlugin = MainApp.getSpecificPlugin(ObjectivesPlugin.class); - if (objectivesPlugin != null) { - ObjectivesPlugin.manualEnacts++; - ObjectivesPlugin.saveProgress(); - } + builder.setPositiveButton(getContext().getString(R.string.ok), (dialog, id) -> { + hideTempRecommendation(); + clearNotification(); + MainApp.getConfigBuilder().applyTBRRequest(finalLastRun.constraintsProcessed, profile, new Callback() { + @Override + public void run() { + if (result.enacted) { + finalLastRun.tbrSetByPump = result; + finalLastRun.lastEnact = new Date(); + finalLastRun.lastOpenModeAccept = new Date(); + NSUpload.uploadDeviceStatus(); + ObjectivesPlugin objectivesPlugin = MainApp.getSpecificPlugin(ObjectivesPlugin.class); + if (objectivesPlugin != null) { + ObjectivesPlugin.manualEnacts++; + ObjectivesPlugin.saveProgress(); } - scheduleUpdateGUI("onClickAcceptTemp"); } - }); - Answers.getInstance().logCustom(new CustomEvent("AcceptTemp")); - } + scheduleUpdateGUI("onClickAcceptTemp"); + } + }); + FabricPrivacy.getInstance().logCustom(new CustomEvent("AcceptTemp")); }); builder.setNegativeButton(getContext().getString(R.string.cancel), null); builder.show(); @@ -712,10 +711,10 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, void onClickQuickwizard() { final BgReading actualBg = DatabaseHelper.actualBg(); final Profile profile = MainApp.getConfigBuilder().getProfile(); - final TempTarget tempTarget = MainApp.getConfigBuilder().getTempTargetFromHistory(); + final TempTarget tempTarget = TreatmentsPlugin.getPlugin().getTempTargetFromHistory(); final QuickWizardEntry quickWizardEntry = OverviewPlugin.getPlugin().quickWizard.getActive(); - if (quickWizardEntry != null && actualBg != null) { + if (quickWizardEntry != null && actualBg != null && profile != null) { quickWizardButton.setVisibility(View.VISIBLE); final BolusWizard wizard = quickWizardEntry.doCalc(profile, tempTarget, actualBg, true); @@ -745,17 +744,17 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, DecimalFormat formatNumber2decimalplaces = new DecimalFormat("0.00"); String confirmMessage = getString(R.string.entertreatmentquestion); - Double insulinAfterConstraints = MainApp.getConfigBuilder().applyBolusConstraints(wizard.calculatedTotalInsulin); - Integer carbsAfterConstraints = MainApp.getConfigBuilder().applyCarbsConstraints(quickWizardEntry.carbs()); + Double insulinAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(wizard.calculatedTotalInsulin)).value(); + Integer carbsAfterConstraints = MainApp.getConstraintChecker().applyCarbsConstraints(new Constraint<>(quickWizardEntry.carbs())).value(); confirmMessage += "\n" + getString(R.string.bolus) + ": " + formatNumber2decimalplaces.format(insulinAfterConstraints) + "U"; confirmMessage += "\n" + getString(R.string.carbs) + ": " + carbsAfterConstraints + "g"; if (!insulinAfterConstraints.equals(wizard.calculatedTotalInsulin) || !carbsAfterConstraints.equals(quickWizardEntry.carbs())) { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); - builder.setTitle(MainApp.sResources.getString(R.string.treatmentdeliveryerror)); + builder.setTitle(MainApp.gs(R.string.treatmentdeliveryerror)); builder.setMessage(getString(R.string.constraints_violation) + "\n" + getString(R.string.changeyourinput)); - builder.setPositiveButton(MainApp.sResources.getString(R.string.ok), null); + builder.setPositiveButton(MainApp.gs(R.string.ok), null); builder.show(); return; } @@ -765,44 +764,44 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, final Context context = getContext(); final AlertDialog.Builder builder = new AlertDialog.Builder(context); accepted = false; - builder.setTitle(MainApp.sResources.getString(R.string.confirmation)); + builder.setTitle(MainApp.gs(R.string.confirmation)); builder.setMessage(confirmMessage); - builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - synchronized (builder) { - if (accepted) { - log.debug("guarding: already accepted"); - return; - } - accepted = true; - if (finalInsulinAfterConstraints > 0 || finalCarbsAfterConstraints > 0) { - if (wizard.superBolus) { - final LoopPlugin activeloop = ConfigBuilderPlugin.getActiveLoop(); - if (activeloop != null) { - activeloop.superBolusTo(System.currentTimeMillis() + 2 * 60L * 60 * 1000); - MainApp.bus().post(new EventRefreshOverview("WizardDialog")); - } - ConfigBuilderPlugin.getCommandQueue().tempBasalPercent(0, 120, true, new Callback() { - @Override - public void run() { - if (!result.success) { - Intent i = new Intent(MainApp.instance(), ErrorHelperActivity.class); - i.putExtra("soundid", R.raw.boluserror); - i.putExtra("status", result.comment); - i.putExtra("title", MainApp.sResources.getString(R.string.tempbasaldeliveryerror)); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - MainApp.instance().startActivity(i); - } - } - }); + builder.setPositiveButton(getString(R.string.ok), (dialog, id) -> { + synchronized (builder) { + if (accepted) { + log.debug("guarding: already accepted"); + return; + } + accepted = true; + if (finalInsulinAfterConstraints > 0 || finalCarbsAfterConstraints > 0) { + if (wizard.superBolus) { + final LoopPlugin loopPlugin = LoopPlugin.getPlugin(); + if (loopPlugin.isEnabled(PluginType.LOOP)) { + loopPlugin.superBolusTo(System.currentTimeMillis() + 2 * 60L * 60 * 1000); + MainApp.bus().post(new EventRefreshOverview("WizardDialog")); } - DetailedBolusInfo detailedBolusInfo = new DetailedBolusInfo(); - detailedBolusInfo.eventType = CareportalEvent.BOLUSWIZARD; - detailedBolusInfo.insulin = finalInsulinAfterConstraints; - detailedBolusInfo.carbs = finalCarbsAfterConstraints; - detailedBolusInfo.context = context; - detailedBolusInfo.boluscalc = boluscalcJSON; - detailedBolusInfo.source = Source.USER; + ConfigBuilderPlugin.getCommandQueue().tempBasalPercent(0, 120, true, profile, new Callback() { + @Override + public void run() { + if (!result.success) { + Intent i = new Intent(MainApp.instance(), ErrorHelperActivity.class); + i.putExtra("soundid", R.raw.boluserror); + i.putExtra("status", result.comment); + i.putExtra("title", MainApp.gs(R.string.tempbasaldeliveryerror)); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + MainApp.instance().startActivity(i); + } + } + }); + } + DetailedBolusInfo detailedBolusInfo = new DetailedBolusInfo(); + detailedBolusInfo.eventType = CareportalEvent.BOLUSWIZARD; + detailedBolusInfo.insulin = finalInsulinAfterConstraints; + detailedBolusInfo.carbs = finalCarbsAfterConstraints; + detailedBolusInfo.context = context; + detailedBolusInfo.boluscalc = boluscalcJSON; + detailedBolusInfo.source = Source.USER; + if (finalInsulinAfterConstraints > 0 || ConfigBuilderPlugin.getActivePump().getPumpDescription().storesCarbInfo) { ConfigBuilderPlugin.getCommandQueue().bolus(detailedBolusInfo, new Callback() { @Override public void run() { @@ -810,14 +809,16 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, Intent i = new Intent(MainApp.instance(), ErrorHelperActivity.class); i.putExtra("soundid", R.raw.boluserror); i.putExtra("status", result.comment); - i.putExtra("title", MainApp.sResources.getString(R.string.treatmentdeliveryerror)); + i.putExtra("title", MainApp.gs(R.string.treatmentdeliveryerror)); i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); MainApp.instance().startActivity(i); } } }); - Answers.getInstance().logCustom(new CustomEvent("QuickWizard")); + } else { + TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo); } + FabricPrivacy.getInstance().logCustom(new CustomEvent("QuickWizard")); } } }); @@ -841,12 +842,9 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, public void onResume() { super.onResume(); MainApp.bus().register(this); - sRefreshLoop = new Runnable() { - @Override - public void run() { - scheduleUpdateGUI("refreshLoop"); - sLoopHandler.postDelayed(sRefreshLoop, 60 * 1000L); - } + sRefreshLoop = () -> { + scheduleUpdateGUI("refreshLoop"); + sLoopHandler.postDelayed(sRefreshLoop, 60 * 1000L); }; sLoopHandler.postDelayed(sRefreshLoop, 60 * 1000L); registerForContextMenu(apsModeView); @@ -894,49 +892,44 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, scheduleUpdateGUI("EventExtendedBolusChange"); } -// Handled by EventAutosensCalculationFinished -// @Subscribe -// public void onStatusEvent(final EventNewBG ev) { -// scheduleUpdateGUI("EventNewBG"); -// } - @Subscribe public void onStatusEvent(final EventNewOpenLoopNotification ev) { scheduleUpdateGUI("EventNewOpenLoopNotification"); } -// Handled by EventAutosensCalculationFinished -// @Subscribe -// public void onStatusEvent(final EventNewBasalProfile ev) { -// scheduleUpdateGUI("EventNewBasalProfile"); -// } - @Subscribe public void onStatusEvent(final EventTempTargetChange ev) { scheduleUpdateGUI("EventTempTargetChange"); } + @Subscribe + public void onStatusEvent(final EventProfileSwitchChange ev) { + scheduleUpdateGUI("EventProfileSwitchChange"); + } + @Subscribe public void onStatusEvent(final EventPumpStatusChanged s) { Activity activity = getActivity(); if (activity != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - updatePumpStatus(s.textStatus()); - } + activity.runOnUiThread(() -> updatePumpStatus(s.textStatus())); + } + + @Subscribe + public void onStatusEvent(final EventIobCalculationProgress e) { + Activity activity = getActivity(); + if (activity != null) + activity.runOnUiThread(() -> { + if (iobCalculationProgressView != null) + iobCalculationProgressView.setText(e.progress); }); } private void hideTempRecommendation() { Activity activity = getActivity(); if (activity != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - if (acceptTempLayout != null) - acceptTempLayout.setVisibility(View.GONE); - } + activity.runOnUiThread(() -> { + if (acceptTempLayout != null) + acceptTempLayout.setVisibility(View.GONE); }); } @@ -962,12 +955,9 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, public void run() { Activity activity = getActivity(); if (activity != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - updateGUI(from); - scheduledUpdate = null; - } + activity.runOnUiThread(() -> { + updateGUI(from); + scheduledUpdate = null; }); } } @@ -991,7 +981,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, if (timeView != null) { //must not exists timeView.setText(DateUtil.timeString(new Date())); } - if (MainApp.getConfigBuilder().getProfile() == null) {// app not initialized yet + if (!MainApp.getConfigBuilder().isProfileValid("Overview")) { pumpStatusView.setText(R.string.noprofileset); pumpStatusLayout.setVisibility(View.VISIBLE); loopStatusLayout.setVisibility(View.GONE); @@ -1007,26 +997,11 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, final PumpInterface pump = ConfigBuilderPlugin.getActivePump(); - Profile profile = MainApp.getConfigBuilder().getProfile(); - String units = profile.getUnits(); + final Profile profile = MainApp.getConfigBuilder().getProfile(); - if (units == null) { - pumpStatusView.setText(R.string.noprofileset); - pumpStatusLayout.setVisibility(View.VISIBLE); - loopStatusLayout.setVisibility(View.GONE); - return; - } - - double lowLineSetting = SP.getDouble("low_mark", Profile.fromMgdlToUnits(OverviewPlugin.bgTargetLow, units)); - double highLineSetting = SP.getDouble("high_mark", Profile.fromMgdlToUnits(OverviewPlugin.bgTargetHigh, units)); - - if (lowLineSetting < 1) - lowLineSetting = Profile.fromMgdlToUnits(76d, units); - if (highLineSetting < 1) - highLineSetting = Profile.fromMgdlToUnits(180d, units); - - final double lowLine = lowLineSetting; - final double highLine = highLineSetting; + final String units = profile.getUnits(); + final double lowLine = OverviewPlugin.getPlugin().determineLowLine(units); + final double highLine = OverviewPlugin.getPlugin().determineHighLine(units); //Start with updating the BG as it is unaffected by loop. // **** BG value **** @@ -1047,40 +1022,46 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, avgdeltaView.setText("øΔ15m: " + Profile.toUnitsString(glucoseStatus.short_avgdelta, glucoseStatus.short_avgdelta * Constants.MGDL_TO_MMOLL, units) + " øΔ40m: " + Profile.toUnitsString(glucoseStatus.long_avgdelta, glucoseStatus.long_avgdelta * Constants.MGDL_TO_MMOLL, units)); } else { - deltaView.setText("Δ " + MainApp.sResources.getString(R.string.notavailable)); + deltaView.setText("Δ " + MainApp.gs(R.string.notavailable)); if (avgdeltaView != null) avgdeltaView.setText(""); } } + Constraint closedLoopEnabled = MainApp.getConstraintChecker().isClosedLoopAllowed(); + // open loop mode final LoopPlugin.LastRun finalLastRun = LoopPlugin.lastRun; if (Config.APS && pump.getPumpDescription().isTempBasalCapable) { apsModeView.setVisibility(View.VISIBLE); apsModeView.setBackgroundColor(MainApp.sResources.getColor(R.color.loopenabled)); apsModeView.setTextColor(Color.BLACK); - final LoopPlugin activeloop = ConfigBuilderPlugin.getActiveLoop(); - if (activeloop != null && activeloop.isEnabled(activeloop.getType()) && activeloop.isSuperBolus()) { + final LoopPlugin loopPlugin = LoopPlugin.getPlugin(); + if (loopPlugin.isEnabled(PluginType.LOOP) && loopPlugin.isSuperBolus()) { apsModeView.setBackgroundColor(MainApp.sResources.getColor(R.color.looppumpsuspended)); - apsModeView.setText(String.format(MainApp.sResources.getString(R.string.loopsuperbolusfor), activeloop.minutesToEndOfSuspend())); + apsModeView.setText(String.format(MainApp.gs(R.string.loopsuperbolusfor), loopPlugin.minutesToEndOfSuspend())); apsModeView.setTextColor(Color.WHITE); - } else if (activeloop != null && activeloop.isEnabled(activeloop.getType()) && activeloop.isSuspended()) { + } else if (loopPlugin.isEnabled(PluginType.LOOP) && loopPlugin.isDisconnected()) { apsModeView.setBackgroundColor(MainApp.sResources.getColor(R.color.looppumpsuspended)); - apsModeView.setText(String.format(MainApp.sResources.getString(R.string.loopsuspendedfor), activeloop.minutesToEndOfSuspend())); + apsModeView.setText(String.format(MainApp.gs(R.string.loopdisconnectedfor), loopPlugin.minutesToEndOfSuspend())); + apsModeView.setTextColor(Color.WHITE); + } else if (loopPlugin.isEnabled(PluginType.LOOP) && loopPlugin.isSuspended()) { + apsModeView.setBackgroundColor(MainApp.sResources.getColor(R.color.looppumpsuspended)); + apsModeView.setText(String.format(MainApp.gs(R.string.loopsuspendedfor), loopPlugin.minutesToEndOfSuspend())); apsModeView.setTextColor(Color.WHITE); } else if (pump.isSuspended()) { apsModeView.setBackgroundColor(MainApp.sResources.getColor(R.color.looppumpsuspended)); - apsModeView.setText(MainApp.sResources.getString(R.string.pumpsuspended)); + apsModeView.setText(MainApp.gs(R.string.pumpsuspended)); apsModeView.setTextColor(Color.WHITE); - } else if (activeloop != null && activeloop.isEnabled(activeloop.getType())) { - if (MainApp.getConfigBuilder().isClosedModeEnabled()) { - apsModeView.setText(MainApp.sResources.getString(R.string.closedloop)); + } else if (loopPlugin.isEnabled(PluginType.LOOP)) { + if (closedLoopEnabled.value()) { + apsModeView.setText(MainApp.gs(R.string.closedloop)); } else { - apsModeView.setText(MainApp.sResources.getString(R.string.openloop)); + apsModeView.setText(MainApp.gs(R.string.openloop)); } } else { apsModeView.setBackgroundColor(MainApp.sResources.getColor(R.color.loopdisabled)); - apsModeView.setText(MainApp.sResources.getString(R.string.disabledloop)); + apsModeView.setText(MainApp.gs(R.string.disabledloop)); apsModeView.setTextColor(Color.WHITE); } } else { @@ -1088,7 +1069,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, } // temp target - TempTarget tempTarget = MainApp.getConfigBuilder().getTempTargetFromHistory(); + TempTarget tempTarget = TreatmentsPlugin.getPlugin().getTempTargetFromHistory(); if (tempTarget != null) { tempTargetView.setTextColor(Color.BLACK); tempTargetView.setBackgroundColor(MainApp.sResources.getColor(R.color.tempTargetBackground)); @@ -1103,12 +1084,12 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, // **** Temp button **** if (acceptTempLayout != null) { - boolean showAcceptButton = !MainApp.getConfigBuilder().isClosedModeEnabled(); // Open mode needed + boolean showAcceptButton = !closedLoopEnabled.value(); // Open mode needed showAcceptButton = showAcceptButton && finalLastRun != null && finalLastRun.lastAPSRun != null; // aps result must exist showAcceptButton = showAcceptButton && (finalLastRun.lastOpenModeAccept == null || finalLastRun.lastOpenModeAccept.getTime() < finalLastRun.lastAPSRun.getTime()); // never accepted or before last result - showAcceptButton = showAcceptButton && finalLastRun.constraintsProcessed.changeRequested; // change is requested + showAcceptButton = showAcceptButton && finalLastRun.constraintsProcessed.isChangeRequested(); // change is requested - if (showAcceptButton && pump.isInitialized() && !pump.isSuspended() && ConfigBuilderPlugin.getActiveLoop() != null) { + if (showAcceptButton && pump.isInitialized() && !pump.isSuspended() && LoopPlugin.getPlugin().isEnabled(PluginType.LOOP)) { acceptTempLayout.setVisibility(View.VISIBLE); acceptTempButton.setText(getContext().getString(R.string.setbasalquestion) + "\n" + finalLastRun.constraintsProcessed); } else { @@ -1116,31 +1097,43 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, } } - // **** Calibration button **** + // **** Calibration & CGM buttons **** + boolean xDripIsBgSource = MainApp.getSpecificPlugin(SourceXdripPlugin.class) != null && MainApp.getSpecificPlugin(SourceXdripPlugin.class).isEnabled(PluginType.BGSOURCE); + boolean g5IsBgSource = MainApp.getSpecificPlugin(SourceDexcomG5Plugin.class) != null && MainApp.getSpecificPlugin(SourceDexcomG5Plugin.class).isEnabled(PluginType.BGSOURCE); + boolean bgAvailable = DatabaseHelper.actualBg() != null; if (calibrationButton != null) { - if (MainApp.getSpecificPlugin(SourceXdripPlugin.class) != null && MainApp.getSpecificPlugin(SourceXdripPlugin.class).isEnabled(PluginBase.BGSOURCE) && profile != null && DatabaseHelper.actualBg() != null) { + if ((xDripIsBgSource || g5IsBgSource) && bgAvailable && SP.getBoolean(R.string.key_show_calibration_button, true)) { calibrationButton.setVisibility(View.VISIBLE); } else { calibrationButton.setVisibility(View.GONE); } } + if (cgmButton != null) { + if (xDripIsBgSource && SP.getBoolean(R.string.key_show_cgm_button, false)) { + cgmButton.setVisibility(View.VISIBLE); + } else if (g5IsBgSource && SP.getBoolean(R.string.key_show_cgm_button, false)) { + cgmButton.setVisibility(View.VISIBLE); + } else { + cgmButton.setVisibility(View.GONE); + } + } - final TemporaryBasal activeTemp = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); + final TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); String basalText = ""; if (shorttextmode) { if (activeTemp != null) { basalText = "T: " + activeTemp.toStringVeryShort(); } else { - basalText = DecimalFormatter.to2Decimal(MainApp.getConfigBuilder().getProfile().getBasal()) + "U/h"; + basalText = DecimalFormatter.to2Decimal(profile.getBasal()) + "U/h"; } baseBasalView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - String fullText = MainApp.sResources.getString(R.string.pump_basebasalrate_label) + ": " + DecimalFormatter.to2Decimal(MainApp.getConfigBuilder().getProfile().getBasal()) + "U/h\n"; + String fullText = MainApp.gs(R.string.pump_basebasalrate_label) + ": " + DecimalFormatter.to2Decimal(profile.getBasal()) + "U/h\n"; if (activeTemp != null) { - fullText += MainApp.sResources.getString(R.string.pump_tempbasal_label) + ": " + activeTemp.toStringFull(); + fullText += MainApp.gs(R.string.pump_tempbasal_label) + ": " + activeTemp.toStringFull(); } - OKDialog.show(getActivity(), MainApp.sResources.getString(R.string.basal), fullText, null); + OKDialog.show(getActivity(), MainApp.gs(R.string.basal), fullText, null); } }); @@ -1149,7 +1142,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, basalText = activeTemp.toStringFull() + " "; } if (Config.NSCLIENT || Config.G5UPLOADER) - basalText += "(" + DecimalFormatter.to2Decimal(MainApp.getConfigBuilder().getProfile().getBasal()) + " U/h)"; + basalText += "(" + DecimalFormatter.to2Decimal(profile.getBasal()) + " U/h)"; else if (pump.getPumpDescription().isTempBasalCapable) { basalText += "(" + DecimalFormatter.to2Decimal(pump.getBaseBasalRate()) + "U/h)"; } @@ -1163,7 +1156,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, baseBasalView.setText(basalText); - final ExtendedBolus extendedBolus = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); + final ExtendedBolus extendedBolus = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(System.currentTimeMillis()); String extendedBolusText = ""; if (extendedBolusView != null) { // must not exists in all layouts if (shorttextmode) { @@ -1174,7 +1167,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, extendedBolusView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - OKDialog.show(getActivity(), MainApp.sResources.getString(R.string.extendedbolus), extendedBolus.toString(), null); + OKDialog.show(getActivity(), MainApp.gs(R.string.extendedbolus), extendedBolus.toString(), null); } }); @@ -1193,17 +1186,14 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, activeProfileView.setText(MainApp.getConfigBuilder().getProfileName()); activeProfileView.setBackgroundColor(Color.GRAY); - tempTargetView.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View view) { - view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - NewNSTreatmentDialog newTTDialog = new NewNSTreatmentDialog(); - final OptionsToShow temptarget = CareportalFragment.TEMPTARGET; - temptarget.executeTempTarget = true; - newTTDialog.setOptions(temptarget, R.string.careportal_temporarytarget); - newTTDialog.show(getFragmentManager(), "NewNSTreatmentDialog"); - return true; - } + tempTargetView.setOnLongClickListener(view -> { + view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + NewNSTreatmentDialog newTTDialog = new NewNSTreatmentDialog(); + final OptionsToShow temptarget = CareportalFragment.TEMPTARGET; + temptarget.executeTempTarget = true; + newTTDialog.setOptions(temptarget, R.string.careportal_temporarytarget); + newTTDialog.show(getFragmentManager(), "NewNSTreatmentDialog"); + return true; }); tempTargetView.setLongClickable(true); @@ -1213,22 +1203,47 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, quickWizardButton.setVisibility(View.VISIBLE); String text = quickWizardEntry.buttonText() + "\n" + DecimalFormatter.to0Decimal(quickWizardEntry.carbs()) + "g"; BolusWizard wizard = quickWizardEntry.doCalc(profile, tempTarget, lastBG, false); - text += " " + DecimalFormatter.to2Decimal(wizard.calculatedTotalInsulin) + "U"; + text += " " + DecimalFormatter.toPumpSupportedBolus(wizard.calculatedTotalInsulin) + "U"; quickWizardButton.setText(text); if (wizard.calculatedTotalInsulin <= 0) quickWizardButton.setVisibility(View.GONE); } else quickWizardButton.setVisibility(View.GONE); - // Bolus and calc button - if (pump.isInitialized() && !pump.isSuspended()) { - wizardButton.setVisibility(View.VISIBLE); - treatmentButton.setVisibility(View.VISIBLE); - } else { - wizardButton.setVisibility(View.GONE); - treatmentButton.setVisibility(View.GONE); + // **** Various treatment buttons **** + if (carbsButton != null) { + if (SP.getBoolean(R.string.key_show_carbs_button, true) + && (!ConfigBuilderPlugin.getActivePump().getPumpDescription().storesCarbInfo || + (pump.isInitialized() && !pump.isSuspended()))) { + carbsButton.setVisibility(View.VISIBLE); + } else { + carbsButton.setVisibility(View.GONE); + } } + if (pump.isInitialized() && !pump.isSuspended()) { + if (treatmentButton != null) { + if (SP.getBoolean(R.string.key_show_treatment_button, false)) { + treatmentButton.setVisibility(View.VISIBLE); + } else { + treatmentButton.setVisibility(View.GONE); + } + } + if (wizardButton != null) { + if (SP.getBoolean(R.string.key_show_wizard_button, true)) { + wizardButton.setVisibility(View.VISIBLE); + } else { + wizardButton.setVisibility(View.GONE); + } + } + if (insulinButton != null) { + if (SP.getBoolean(R.string.key_show_insulin_button, true)) { + insulinButton.setVisibility(View.VISIBLE); + } else { + insulinButton.setVisibility(View.GONE); + } + } + } // **** BG value **** if (lastBG == null) { //left this here as it seems you want to exit at this point if it is null... @@ -1241,27 +1256,22 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, flag &= ~Paint.STRIKE_THRU_TEXT_FLAG; bgView.setPaintFlags(flag); - Long agoMsec = System.currentTimeMillis() - lastBG.date; - int agoMin = (int) (agoMsec / 60d / 1000d); - timeAgoView.setText(String.format(MainApp.sResources.getString(R.string.minago), agoMin)); + timeAgoView.setText(DateUtil.minAgo(lastBG.date)); // iob - MainApp.getConfigBuilder().updateTotalIOBTreatments(); - MainApp.getConfigBuilder().updateTotalIOBTempBasals(); - final IobTotal bolusIob = MainApp.getConfigBuilder().getLastCalculationTreatments().round(); - final IobTotal basalIob = MainApp.getConfigBuilder().getLastCalculationTempBasals().round(); + TreatmentsPlugin.getPlugin().updateTotalIOBTreatments(); + TreatmentsPlugin.getPlugin().updateTotalIOBTempBasals(); + final IobTotal bolusIob = TreatmentsPlugin.getPlugin().getLastCalculationTreatments().round(); + final IobTotal basalIob = TreatmentsPlugin.getPlugin().getLastCalculationTempBasals().round(); if (shorttextmode) { String iobtext = DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob) + "U"; iobView.setText(iobtext); - iobView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - String iobtext = DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob) + "U\n" - + getString(R.string.bolus) + ": " + DecimalFormatter.to2Decimal(bolusIob.iob) + "U\n" - + getString(R.string.basal) + ": " + DecimalFormatter.to2Decimal(basalIob.basaliob) + "U\n"; - OKDialog.show(getActivity(), MainApp.sResources.getString(R.string.iob), iobtext, null); - } + iobView.setOnClickListener(v -> { + String iobtext1 = DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob) + "U\n" + + getString(R.string.bolus) + ": " + DecimalFormatter.to2Decimal(bolusIob.iob) + "U\n" + + getString(R.string.basal) + ": " + DecimalFormatter.to2Decimal(basalIob.basaliob) + "U\n"; + OKDialog.show(getActivity(), MainApp.gs(R.string.iob), iobtext1, null); }); } else if (MainApp.sResources.getBoolean(R.bool.isTablet)) { String iobtext = DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob) + "U (" @@ -1277,170 +1287,156 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, // cob if (cobView != null) { // view must not exists - String cobText = ""; - AutosensData autosensData = IobCobCalculatorPlugin.getLastAutosensData("Overview COB"); - if (autosensData != null) - cobText = (int) autosensData.cob + " g"; + String cobText = MainApp.gs(R.string.value_unavailable_short); + CobInfo cobInfo = IobCobCalculatorPlugin.getPlugin().getCobInfo(false, "Overview COB"); + if (cobInfo.displayCob != null) { + cobText = DecimalFormatter.to0Decimal(cobInfo.displayCob); + if (cobInfo.futureCarbs > 0) + cobText += "(" + DecimalFormatter.to0Decimal(cobInfo.futureCarbs) + ")"; + } cobView.setText(cobText); } - final boolean showPrediction = showPredictionCheckbox.isChecked() && finalLastRun != null && finalLastRun.constraintsProcessed.getClass().equals(DetermineBasalResultAMA.class); - if (MainApp.getSpecificPlugin(OpenAPSAMAPlugin.class) != null && MainApp.getSpecificPlugin(OpenAPSAMAPlugin.class).isEnabled(PluginBase.APS)) { - showPredictionCheckbox.setVisibility(View.VISIBLE); - showPredictionLabel.setVisibility(View.VISIBLE); - } else { - showPredictionCheckbox.setVisibility(View.GONE); - showPredictionLabel.setVisibility(View.GONE); - } - + final boolean predictionsAvailable = finalLastRun != null && finalLastRun.request.hasPredictions; // pump status from ns if (pumpDeviceStatusView != null) { pumpDeviceStatusView.setText(NSDeviceStatus.getInstance().getPumpStatus()); - pumpDeviceStatusView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - OKDialog.show(getActivity(), MainApp.sResources.getString(R.string.pump), NSDeviceStatus.getInstance().getExtendedPumpStatus(), null); - } - }); + pumpDeviceStatusView.setOnClickListener(v -> OKDialog.show(getActivity(), MainApp.gs(R.string.pump), NSDeviceStatus.getInstance().getExtendedPumpStatus(), null)); } // OpenAPS status from ns if (openapsDeviceStatusView != null) { openapsDeviceStatusView.setText(NSDeviceStatus.getInstance().getOpenApsStatus()); - openapsDeviceStatusView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - OKDialog.show(getActivity(), MainApp.sResources.getString(R.string.openaps), NSDeviceStatus.getInstance().getExtendedOpenApsStatus(), null); - } - }); + openapsDeviceStatusView.setOnClickListener(v -> OKDialog.show(getActivity(), MainApp.gs(R.string.openaps), NSDeviceStatus.getInstance().getExtendedOpenApsStatus(), null)); } // Uploader status from ns if (uploaderDeviceStatusView != null) { uploaderDeviceStatusView.setText(NSDeviceStatus.getInstance().getUploaderStatus()); - uploaderDeviceStatusView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - OKDialog.show(getActivity(), MainApp.sResources.getString(R.string.uploader), NSDeviceStatus.getInstance().getExtendedUploaderStatus(), null); - } - }); + uploaderDeviceStatusView.setOnClickListener(v -> OKDialog.show(getActivity(), MainApp.gs(R.string.uploader), NSDeviceStatus.getInstance().getExtendedUploaderStatus(), null)); } // ****** GRAPH ******* - new Thread(new Runnable() { - @Override - public void run() { - // allign to hours - Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(System.currentTimeMillis()); - calendar.set(Calendar.MILLISECOND, 0); - calendar.set(Calendar.SECOND, 0); - calendar.set(Calendar.MINUTE, 0); - calendar.add(Calendar.HOUR, 1); + new Thread(() -> { + // allign to hours + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(System.currentTimeMillis()); + calendar.set(Calendar.MILLISECOND, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.add(Calendar.HOUR, 1); - int hoursToFetch; - final long toTime; - final long fromTime; - final long endTime; - if (showPrediction) { - int predHours = (int) (Math.ceil(((DetermineBasalResultAMA) finalLastRun.constraintsProcessed).getLatestPredictionsTime() - System.currentTimeMillis()) / (60 * 60 * 1000)); - predHours = Math.min(2, predHours); - predHours = Math.max(0, predHours); - hoursToFetch = rangeToDisplay - predHours; - toTime = calendar.getTimeInMillis() + 100000; // little bit more to avoid wrong rounding - Graphview specific - fromTime = toTime - hoursToFetch * 60 * 60 * 1000L; - endTime = toTime + predHours * 60 * 60 * 1000L; - } else { - hoursToFetch = rangeToDisplay; - toTime = calendar.getTimeInMillis() + 100000; // little bit more to avoid wrong rounding - Graphview specific - fromTime = toTime - hoursToFetch * 60 * 60 * 1000L; - endTime = toTime; - } + int hoursToFetch; + final long toTime; + final long fromTime; + final long endTime; + if (predictionsAvailable && SP.getBoolean("showprediction", false)) { + int predHours = (int) (Math.ceil(finalLastRun.constraintsProcessed.getLatestPredictionsTime() - System.currentTimeMillis()) / (60 * 60 * 1000)); + predHours = Math.min(2, predHours); + predHours = Math.max(0, predHours); + hoursToFetch = rangeToDisplay - predHours; + toTime = calendar.getTimeInMillis() + 100000; // little bit more to avoid wrong rounding - Graphview specific + fromTime = toTime - hoursToFetch * 60 * 60 * 1000L; + endTime = toTime + predHours * 60 * 60 * 1000L; + } else { + hoursToFetch = rangeToDisplay; + toTime = calendar.getTimeInMillis() + 100000; // little bit more to avoid wrong rounding - Graphview specific + fromTime = toTime - hoursToFetch * 60 * 60 * 1000L; + endTime = toTime; + } - final long now = System.currentTimeMillis(); + final long now = System.currentTimeMillis(); - // ------------------ 1st graph - Profiler.log(log, from + " - 1st graph - START", updateGUIStart); + // ------------------ 1st graph + Profiler.log(log, from + " - 1st graph - START", updateGUIStart); - final GraphData graphData = new GraphData(bgGraph); + final GraphData graphData = new GraphData(bgGraph, IobCobCalculatorPlugin.getPlugin()); - // **** In range Area **** - graphData.addInRangeArea(fromTime, endTime, lowLine, highLine); + // **** In range Area **** + graphData.addInRangeArea(fromTime, endTime, lowLine, highLine); - // **** BG **** - if (showPrediction) - graphData.addBgReadings(fromTime, toTime, lowLine, highLine, (DetermineBasalResultAMA) finalLastRun.constraintsProcessed); - else - graphData.addBgReadings(fromTime, toTime, lowLine, highLine, null); + // **** BG **** + if (predictionsAvailable && SP.getBoolean("showprediction", false)) + graphData.addBgReadings(fromTime, toTime, lowLine, highLine, finalLastRun.constraintsProcessed); + else + graphData.addBgReadings(fromTime, toTime, lowLine, highLine, null); - // set manual x bounds to have nice steps - graphData.formatAxis(fromTime, endTime); + // set manual x bounds to have nice steps + graphData.formatAxis(fromTime, endTime); - // Treatments - graphData.addTreatments(fromTime, endTime); + // Treatments + graphData.addTreatments(fromTime, endTime); - // add basal data - if (pump.getPumpDescription().isTempBasalCapable && showBasalsCheckbox.isChecked()) { - graphData.addBasals(fromTime, now, lowLine / graphData.maxY / 1.2d); - } + // add basal data + if (pump.getPumpDescription().isTempBasalCapable && SP.getBoolean("showbasals", true)) { + graphData.addBasals(fromTime, now, lowLine / graphData.maxY / 1.2d); + } - // **** NOW line **** - graphData.addNowLine(now); + // add target line + graphData.addTargetLine(fromTime, toTime, profile); - // ------------------ 2nd graph - Profiler.log(log, from + " - 2nd graph - START", updateGUIStart); + // **** NOW line **** + graphData.addNowLine(now); - final GraphData secondGraphData = new GraphData(iobGraph); + // ------------------ 2nd graph + Profiler.log(log, from + " - 2nd graph - START", updateGUIStart); - boolean useIobForScale = false; - boolean useCobForScale = false; - boolean useDevForScale = false; - boolean useRatioForScale = false; + final GraphData secondGraphData = new GraphData(iobGraph, IobCobCalculatorPlugin.getPlugin()); - if (showIobCheckbox.isChecked()) { - useIobForScale = true; - } else if (showCobCheckbox.isChecked()) { - useCobForScale = true; - } else if (showDeviationsCheckbox.isChecked()) { - useDevForScale = true; - } else if (showRatiosCheckbox.isChecked()) { - useRatioForScale = true; - } + boolean useIobForScale = false; + boolean useCobForScale = false; + boolean useDevForScale = false; + boolean useRatioForScale = false; + boolean useDSForScale = false; - if (showIobCheckbox.isChecked()) - secondGraphData.addIob(fromTime, now, useIobForScale, 1d); - if (showCobCheckbox.isChecked()) - secondGraphData.addCob(fromTime, now, useCobForScale, useCobForScale ? 1d : 0.5d); - if (showDeviationsCheckbox.isChecked()) - secondGraphData.addDeviations(fromTime, now, useDevForScale, 1d); - if (showRatiosCheckbox.isChecked()) - secondGraphData.addRatio(fromTime, now, useRatioForScale, 1d); + if (SP.getBoolean("showiob", true)) { + useIobForScale = true; + } else if (SP.getBoolean("showcob", true)) { + useCobForScale = true; + } else if (SP.getBoolean("showdeviations", false)) { + useDevForScale = true; + } else if (SP.getBoolean("showratios", false)) { + useRatioForScale = true; + } else if (SP.getBoolean("showdevslope", false)) { + useDSForScale = true; + } - // **** NOW line **** - // set manual x bounds to have nice steps - secondGraphData.formatAxis(fromTime, endTime); - secondGraphData.addNowLine(now); + if (SP.getBoolean("showiob", true)) + secondGraphData.addIob(fromTime, now, useIobForScale, 1d); + if (SP.getBoolean("showcob", true)) + secondGraphData.addCob(fromTime, now, useCobForScale, useCobForScale ? 1d : 0.5d); + if (SP.getBoolean("showdeviations", false)) + secondGraphData.addDeviations(fromTime, now, useDevForScale, 1d); + if (SP.getBoolean("showratios", false)) + secondGraphData.addRatio(fromTime, now, useRatioForScale, 1d); + if (SP.getBoolean("showdevslope", false)) + secondGraphData.addDeviationSlope(fromTime, now, useDSForScale, 1d); - // do GUI update - FragmentActivity activity = getActivity(); - if (activity != null) { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - if (showIobCheckbox.isChecked() || showCobCheckbox.isChecked() || showDeviationsCheckbox.isChecked() || showRatiosCheckbox.isChecked()) { - iobGraph.setVisibility(View.VISIBLE); - } else { - iobGraph.setVisibility(View.GONE); - } - // finally enforce drawing of graphs - graphData.performUpdate(); - secondGraphData.performUpdate(); - Profiler.log(log, from + " - onDataChanged", updateGUIStart); - } - }); - } + // **** NOW line **** + // set manual x bounds to have nice steps + secondGraphData.formatAxis(fromTime, endTime); + secondGraphData.addNowLine(now); + + // do GUI update + FragmentActivity activity = getActivity(); + if (activity != null) { + activity.runOnUiThread(() -> { + if (SP.getBoolean("showiob", true) + || SP.getBoolean("showcob", true) + || SP.getBoolean("showdeviations", false) + || SP.getBoolean("showratios", false) + || SP.getBoolean("showdevslope", false)) { + iobGraph.setVisibility(View.VISIBLE); + } else { + iobGraph.setVisibility(View.GONE); + } + // finally enforce drawing of graphs + graphData.performUpdate(); + secondGraphData.performUpdate(); + Profiler.log(log, from + " - onDataChanged", updateGUIStart); + }); } }).start(); @@ -1448,108 +1444,18 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, } //Notifications - static class RecyclerViewAdapter extends RecyclerView.Adapter { - - List notificationsList; - - RecyclerViewAdapter(List notificationsList) { - this.notificationsList = notificationsList; - } - - @Override - public NotificationsViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { - View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.overview_notification_item, viewGroup, false); - return new NotificationsViewHolder(v); - } - - @Override - public void onBindViewHolder(NotificationsViewHolder holder, int position) { - Notification notification = notificationsList.get(position); - holder.dismiss.setTag(notification); - if (Objects.equals(notification.text, MainApp.sResources.getString(R.string.nsalarm_staledata))) - holder.dismiss.setText("snooze"); - holder.text.setText(notification.text); - holder.time.setText(DateUtil.timeString(notification.date)); - if (notification.level == Notification.URGENT) - holder.cv.setBackgroundColor(ContextCompat.getColor(MainApp.instance(), R.color.notificationUrgent)); - else if (notification.level == Notification.NORMAL) - holder.cv.setBackgroundColor(ContextCompat.getColor(MainApp.instance(), R.color.notificationNormal)); - else if (notification.level == Notification.LOW) - holder.cv.setBackgroundColor(ContextCompat.getColor(MainApp.instance(), R.color.notificationLow)); - else if (notification.level == Notification.INFO) - holder.cv.setBackgroundColor(ContextCompat.getColor(MainApp.instance(), R.color.notificationInfo)); - else if (notification.level == Notification.ANNOUNCEMENT) - holder.cv.setBackgroundColor(ContextCompat.getColor(MainApp.instance(), R.color.notificationAnnouncement)); - } - - @Override - public int getItemCount() { - return notificationsList.size(); - } - - @Override - public void onAttachedToRecyclerView(RecyclerView recyclerView) { - super.onAttachedToRecyclerView(recyclerView); - } - - static class NotificationsViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { - CardView cv; - TextView time; - TextView text; - Button dismiss; - - NotificationsViewHolder(View itemView) { - super(itemView); - cv = (CardView) itemView.findViewById(R.id.notification_cardview); - time = (TextView) itemView.findViewById(R.id.notification_time); - text = (TextView) itemView.findViewById(R.id.notification_text); - dismiss = (Button) itemView.findViewById(R.id.notification_dismiss); - dismiss.setOnClickListener(this); - } - - @Override - public void onClick(View v) { - Notification notification = (Notification) v.getTag(); - switch (v.getId()) { - case R.id.notification_dismiss: - MainApp.bus().post(new EventDismissNotification(notification.id)); - if (notification.nsAlarm != null) { - BroadcastAckAlarm.handleClearAlarm(notification.nsAlarm, MainApp.instance().getApplicationContext(), 60 * 60 * 1000L); - } - // Adding current time to snooze if we got staleData - log.debug("Notification text is: " + notification.text); - if (notification.text.equals(MainApp.sResources.getString(R.string.nsalarm_staledata))) { - NotificationStore nstore = OverviewPlugin.getPlugin().notificationStore; - long msToSnooze = SP.getInt("nsalarm_staledatavalue", 15) * 60 * 1000L; - log.debug("snooze nsalarm_staledatavalue in minutes is " + SP.getInt("nsalarm_staledatavalue", 15) + "\n in ms is: " + msToSnooze + " currentTimeMillis is: " + System.currentTimeMillis()); - nstore.snoozeTo(System.currentTimeMillis() + (SP.getInt("nsalarm_staledatavalue", 15) * 60 * 1000L)); - } - break; - } - } - } - - } void updateNotifications() { - Activity activity = getActivity(); - if (activity != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - NotificationStore nstore = OverviewPlugin.getPlugin().notificationStore; - nstore.removeExpired(); - nstore.unSnooze(); - if (nstore.store.size() > 0) { - RecyclerViewAdapter adapter = new RecyclerViewAdapter(nstore.store); - notificationsView.setAdapter(adapter); - notificationsView.setVisibility(View.VISIBLE); - } else { - notificationsView.setVisibility(View.GONE); - } - } - }); + NotificationStore nstore = OverviewPlugin.getPlugin().notificationStore; + nstore.removeExpired(); + nstore.unSnooze(); + if (nstore.store.size() > 0) { + NotificationRecyclerViewAdapter adapter = new NotificationRecyclerViewAdapter(nstore.store); + notificationsView.setAdapter(adapter); + notificationsView.setVisibility(View.VISIBLE); + } else { + notificationsView.setVisibility(View.GONE); + } } - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewPlugin.java index eb31ae66ed..67e494a76d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewPlugin.java @@ -9,9 +9,12 @@ import org.slf4j.LoggerFactory; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.QuickWizard; import info.nightscout.androidaps.events.EventRefreshOverview; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.Overview.notifications.NotificationStore; @@ -20,13 +23,12 @@ import info.nightscout.utils.SP; /** * Created by mike on 05.08.2016. */ -public class OverviewPlugin implements PluginBase { +public class OverviewPlugin extends PluginBase { private static Logger log = LoggerFactory.getLogger(OverviewPlugin.class); private static OverviewPlugin overviewPlugin = new OverviewPlugin(); public static OverviewPlugin getPlugin() { - if (overviewPlugin == null) overviewPlugin = new OverviewPlugin(); return overviewPlugin; @@ -40,86 +42,38 @@ public class OverviewPlugin implements PluginBase { public NotificationStore notificationStore = new NotificationStore(); public OverviewPlugin() { + super(new PluginDescription() + .mainType(PluginType.GENERAL) + .fragmentClass(OverviewFragment.class.getName()) + .alwayVisible(true) + .alwaysEnabled(true) + .pluginName(R.string.overview) + .shortName(R.string.overview_shortname) + .preferencesId(R.xml.pref_overview) + ); String storedData = SP.getString("QuickWizard", "[]"); try { quickWizard.setData(new JSONArray(storedData)); } catch (JSONException e) { log.error("Unhandled exception", e); } + } + + @Override + protected void onStart() { MainApp.bus().register(this); + super.onStart(); } @Override - public String getFragmentClass() { - return OverviewFragment.class.getName(); + protected void onStop() { + MainApp.bus().unregister(this); } - @Override - public String getName() { - return MainApp.instance().getString(R.string.overview); - } - - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.overview_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == GENERAL; - } - - @Override - public boolean isVisibleInTabs(int type) { - return true; - } - - @Override - public boolean canBeHidden(int type) { - return false; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return false; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - // Always enabled - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - // Always visible - } - - @Override - public int getPreferencesId() { - return -1; - } - - @Override - public int getType() { - return PluginBase.GENERAL; - } - - @Subscribe public void onStatusEvent(final EventNewNotification n) { - notificationStore.add(n.notification); - MainApp.bus().post(new EventRefreshOverview("EventNewNotification")); + if (notificationStore.add(n.notification)) + MainApp.bus().post(new EventRefreshOverview("EventNewNotification")); } @Subscribe @@ -128,4 +82,26 @@ public class OverviewPlugin implements PluginBase { MainApp.bus().post(new EventRefreshOverview("EventDismissNotification")); } + public double determineHighLine(String units) { + double highLineSetting = SP.getDouble("high_mark", Profile.fromMgdlToUnits(OverviewPlugin.bgTargetHigh, units)); + if (highLineSetting < 1) + highLineSetting = Profile.fromMgdlToUnits(180d, units); + return highLineSetting; + } + + public double determineLowLine() { + Profile profile = MainApp.getConfigBuilder().getProfile(); + if (profile == null) { + return bgTargetLow; + } + return determineLowLine(profile.getUnits()); + } + + public double determineLowLine(String units) { + double lowLineSetting = SP.getDouble("low_mark", Profile.fromMgdlToUnits(OverviewPlugin.bgTargetLow, units)); + if (lowLineSetting < 1) + lowLineSetting = Profile.fromMgdlToUnits(76d, units); + return lowLineSetting; + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/events/EventOverviewBolusProgress.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/events/EventOverviewBolusProgress.java index e0fd53a1fd..6f9526369e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/events/EventOverviewBolusProgress.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/events/EventOverviewBolusProgress.java @@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.Overview.events; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.androidaps.events.Event; public class EventOverviewBolusProgress extends Event { @@ -11,11 +11,16 @@ public class EventOverviewBolusProgress extends Event { public String status = ""; public Treatment t = null; public int percent = 0; + public int bolusId; private static EventOverviewBolusProgress eventOverviewBolusProgress = null; public EventOverviewBolusProgress() { } + public boolean isSMB(){ + return (t != null) && t.isSMB; + } + public static EventOverviewBolusProgress getInstance() { if(eventOverviewBolusProgress == null) { eventOverviewBolusProgress = new EventOverviewBolusProgress(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphData/GraphData.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphData/GraphData.java index 5d5a0832d5..25e7cb4af8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphData/GraphData.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphData/GraphData.java @@ -12,7 +12,6 @@ import com.jjoe64.graphview.series.LineGraphSeries; import com.jjoe64.graphview.series.Series; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import info.nightscout.androidaps.Constants; @@ -23,12 +22,13 @@ import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.CareportalEvent; import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.ProfileSwitch; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.db.TempTarget; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensData; -import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.BasalData; -import info.nightscout.androidaps.plugins.OpenAPSAMA.DetermineBasalResultAMA; +import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; +import info.nightscout.androidaps.plugins.Loop.APSResult; +import info.nightscout.androidaps.plugins.Loop.LoopPlugin; import info.nightscout.androidaps.plugins.Overview.graphExtensions.AreaGraphSeries; import info.nightscout.androidaps.plugins.Overview.graphExtensions.DataPointWithLabelInterface; import info.nightscout.androidaps.plugins.Overview.graphExtensions.DoubleDataPoint; @@ -37,6 +37,8 @@ import info.nightscout.androidaps.plugins.Overview.graphExtensions.PointsWithLab import info.nightscout.androidaps.plugins.Overview.graphExtensions.Scale; import info.nightscout.androidaps.plugins.Overview.graphExtensions.ScaledDataPoint; import info.nightscout.androidaps.plugins.Overview.graphExtensions.TimeAsXAxisLabelFormatter; +import info.nightscout.androidaps.plugins.Treatments.Treatment; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.Round; /** @@ -51,12 +53,15 @@ public class GraphData { private String units; private List series = new ArrayList<>(); - public GraphData(GraphView graph) { + private IobCobCalculatorPlugin iobCobCalculatorPlugin; + + public GraphData(GraphView graph, IobCobCalculatorPlugin iobCobCalculatorPlugin) { units = MainApp.getConfigBuilder().getProfileUnits(); this.graph = graph; + this.iobCobCalculatorPlugin = iobCobCalculatorPlugin; } - public void addBgReadings(long fromTime, long toTime, double lowLine, double highLine, DetermineBasalResultAMA amaResult) { + public void addBgReadings(long fromTime, long toTime, double lowLine, double highLine, APSResult apsResult) { double maxBgValue = 0d; bgReadingsArray = MainApp.getDbHelper().getBgreadingsDataFromTime(fromTime, true); List bgListArray = new ArrayList<>(); @@ -65,14 +70,12 @@ public class GraphData { return; } - Iterator it = bgReadingsArray.iterator(); - while (it.hasNext()) { - BgReading bg = it.next(); + for (BgReading bg : bgReadingsArray) { if (bg.value > maxBgValue) maxBgValue = bg.value; bgListArray.add(bg); } - if (amaResult != null) { - List predArray = amaResult.getPredictions(); + if (apsResult != null) { + List predArray = apsResult.getPredictions(); bgListArray.addAll(predArray); } @@ -105,7 +108,7 @@ public class GraphData { inRangeAreaSeries = new AreaGraphSeries<>(inRangeAreaDataPoints); inRangeAreaSeries.setColor(0); inRangeAreaSeries.setDrawBackground(true); - inRangeAreaSeries.setBackgroundColor(MainApp.sResources.getColor(R.color.inrangebackground)); + inRangeAreaSeries.setBackgroundColor(MainApp.gc(R.color.inrangebackground)); addSeries(inRangeAreaSeries); } @@ -125,11 +128,11 @@ public class GraphData { List basalLineArray = new ArrayList<>(); List absoluteBasalLineArray = new ArrayList<>(); double lastLineBasal = 0; - double lastAbsoluteLineBasal = 0; + double lastAbsoluteLineBasal = -1; double lastBaseBasal = 0; double lastTempBasal = 0; for (long time = fromTime; time < toTime; time += 60 * 1000L) { - BasalData basalData = IobCobCalculatorPlugin.getBasalData(time); + BasalData basalData = IobCobCalculatorPlugin.getPlugin().getBasalData(time); double baseBasalValue = basalData.basal; double absoluteLineValue = baseBasalValue; double tempBasalValue = 0; @@ -169,7 +172,7 @@ public class GraphData { lastAbsoluteLineBasal = absoluteLineValue; lastLineBasal = baseBasalValue; lastTempBasal = tempBasalValue; - maxBasalValueFound = Math.max(maxBasalValueFound, basal); + maxBasalValueFound = Math.max(maxBasalValueFound, Math.max(tempBasalValue, baseBasalValue)); } basalLineArray.add(new ScaledDataPoint(toTime, lastLineBasal, basalScale)); @@ -181,14 +184,14 @@ public class GraphData { baseBasal = baseBasalArray.toArray(baseBasal); baseBasalsSeries = new LineGraphSeries<>(baseBasal); baseBasalsSeries.setDrawBackground(true); - baseBasalsSeries.setBackgroundColor(MainApp.sResources.getColor(R.color.basebasal)); + baseBasalsSeries.setBackgroundColor(MainApp.gc(R.color.basebasal)); baseBasalsSeries.setThickness(0); ScaledDataPoint[] tempBasal = new ScaledDataPoint[tempBasalArray.size()]; tempBasal = tempBasalArray.toArray(tempBasal); tempBasalsSeries = new LineGraphSeries<>(tempBasal); tempBasalsSeries.setDrawBackground(true); - tempBasalsSeries.setBackgroundColor(MainApp.sResources.getColor(R.color.tempbasal)); + tempBasalsSeries.setBackgroundColor(MainApp.gc(R.color.tempbasal)); tempBasalsSeries.setThickness(0); ScaledDataPoint[] basalLine = new ScaledDataPoint[basalLineArray.size()]; @@ -198,7 +201,7 @@ public class GraphData { paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(MainApp.instance().getApplicationContext().getResources().getDisplayMetrics().scaledDensity * 2); paint.setPathEffect(new DashPathEffect(new float[]{2, 4}, 0)); - paint.setColor(MainApp.sResources.getColor(R.color.basal)); + paint.setColor(MainApp.gc(R.color.basal)); basalsLineSeries.setCustomPaint(paint); ScaledDataPoint[] absoluteBasalLine = new ScaledDataPoint[absoluteBasalLineArray.size()]; @@ -207,7 +210,7 @@ public class GraphData { Paint absolutePaint = new Paint(); absolutePaint.setStyle(Paint.Style.STROKE); absolutePaint.setStrokeWidth(MainApp.instance().getApplicationContext().getResources().getDisplayMetrics().scaledDensity * 2); - absolutePaint.setColor(MainApp.sResources.getColor(R.color.basal)); + absolutePaint.setColor(MainApp.gc(R.color.basal)); absoluteBasalsLineSeries.setCustomPaint(absolutePaint); basalScale.setMultiplier(maxY * scale / maxBasalValueFound); @@ -218,10 +221,54 @@ public class GraphData { addSeries(absoluteBasalsLineSeries); } + public void addTargetLine(long fromTime, long toTime, Profile profile) { + LineGraphSeries targetsSeries; + + Scale targetsScale = new Scale(); + targetsScale.setMultiplier(1); + + List targetsSeriesArray = new ArrayList<>(); + double lastTarget = 0; + + if (LoopPlugin.lastRun != null && LoopPlugin.lastRun.constraintsProcessed != null) { + APSResult apsResult = LoopPlugin.lastRun.constraintsProcessed; + long latestPredictionsTime = apsResult.getLatestPredictionsTime(); + if (latestPredictionsTime > toTime) { + toTime = latestPredictionsTime; + } + } + + for (long time = fromTime; time < toTime; time += 60 * 1000L) { + TempTarget tt = TreatmentsPlugin.getPlugin().getTempTargetFromHistory(time); + double value; + if (tt == null) { + value = (profile.getTargetLow(time) + profile.getTargetHigh(time)) / 2; + } else { + value = tt.target(); + value = Profile.fromMgdlToUnits(value, profile.getUnits()); + } + if (lastTarget > 0 && lastTarget != value) { + targetsSeriesArray.add(new DataPoint(time, lastTarget)); + } + lastTarget = value; + + targetsSeriesArray.add(new DataPoint(time, value)); + } + + DataPoint[] targets = new DataPoint[targetsSeriesArray.size()]; + targets = targetsSeriesArray.toArray(targets); + targetsSeries = new LineGraphSeries<>(targets); + targetsSeries.setDrawBackground(false); + targetsSeries.setColor(MainApp.gc(R.color.tempTargetBackground)); + targetsSeries.setThickness(2); + + addSeries(targetsSeries); + } + public void addTreatments(long fromTime, long endTime) { List filteredTreatments = new ArrayList<>(); - List treatments = MainApp.getConfigBuilder().getTreatmentsFromHistory(); + List treatments = TreatmentsPlugin.getPlugin().getTreatmentsFromHistory(); for (int tx = 0; tx < treatments.size(); tx++) { Treatment t = treatments.get(tx); @@ -231,7 +278,7 @@ public class GraphData { } // ProfileSwitch - List profileSwitches = MainApp.getConfigBuilder().getProfileSwitchesFromHistory().getList(); + List profileSwitches = TreatmentsPlugin.getPlugin().getProfileSwitchesFromHistory().getList(); for (int tx = 0; tx < profileSwitches.size(); tx++) { DataPointWithLabelInterface t = profileSwitches.get(tx); @@ -241,7 +288,7 @@ public class GraphData { // Extended bolus if (!ConfigBuilderPlugin.getActivePump().isFakingTempsByExtendedBoluses()) { - List extendedBoluses = MainApp.getConfigBuilder().getExtendedBolusesFromHistory().getList(); + List extendedBoluses = TreatmentsPlugin.getPlugin().getExtendedBolusesFromHistory().getList(); for (int tx = 0; tx < extendedBoluses.size(); tx++) { DataPointWithLabelInterface t = extendedBoluses.get(tx); @@ -267,7 +314,7 @@ public class GraphData { addSeries(new PointsWithLabelGraphSeries<>(treatmentsArray)); } - double getNearestBg(long date) { + private double getNearestBg(long date) { double bg = 0; for (int r = bgReadingsArray.size() - 1; r >= 0; r--) { BgReading reading = bgReadingsArray.get(r); @@ -287,7 +334,10 @@ public class GraphData { Scale iobScale = new Scale(); for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { - double iob = IobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time).iob; + Profile profile = MainApp.getConfigBuilder().getProfile(time); + double iob = 0d; + if (profile != null) + iob = IobCobCalculatorPlugin.getPlugin().calculateFromTreatmentsAndTempsSynchronized(time, profile).iob; if (Math.abs(lastIob - iob) > 0.02) { if (Math.abs(lastIob - iob) > 0.2) iobArray.add(new ScaledDataPoint(time, lastIob, iobScale)); @@ -301,8 +351,8 @@ public class GraphData { iobData = iobArray.toArray(iobData); iobSeries = new FixedLineGraphSeries<>(iobData); iobSeries.setDrawBackground(true); - iobSeries.setBackgroundColor(0x80FFFFFF & MainApp.sResources.getColor(R.color.iob)); //50% - iobSeries.setColor(MainApp.sResources.getColor(R.color.iob)); + iobSeries.setBackgroundColor(0x80FFFFFF & MainApp.gc(R.color.iob)); //50% + iobSeries.setColor(MainApp.gc(R.color.iob)); iobSeries.setThickness(3); if (useForScale) @@ -315,6 +365,7 @@ public class GraphData { // scale in % of vertical size (like 0.3) public void addCob(long fromTime, long toTime, boolean useForScale, double scale) { + List minFailoverActiveList = new ArrayList<>(); FixedLineGraphSeries cobSeries; List cobArray = new ArrayList<>(); Double maxCobValueFound = 0d; @@ -322,7 +373,7 @@ public class GraphData { Scale cobScale = new Scale(); for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { - AutosensData autosensData = IobCobCalculatorPlugin.getAutosensData(time); + AutosensData autosensData = IobCobCalculatorPlugin.getPlugin().getAutosensData(time); if (autosensData != null) { int cob = (int) autosensData.cob; if (cob != lastCob) { @@ -332,6 +383,11 @@ public class GraphData { maxCobValueFound = Math.max(maxCobValueFound, cob); lastCob = cob; } + if (autosensData.failoverToMinAbsorbtionRate) { + autosensData.setScale(cobScale); + autosensData.setChartTime(time); + minFailoverActiveList.add(autosensData); + } } } @@ -340,8 +396,8 @@ public class GraphData { cobData = cobArray.toArray(cobData); cobSeries = new FixedLineGraphSeries<>(cobData); cobSeries.setDrawBackground(true); - cobSeries.setBackgroundColor(0xB0FFFFFF & MainApp.sResources.getColor(R.color.cob)); //50% - cobSeries.setColor(MainApp.sResources.getColor(R.color.cob)); + cobSeries.setBackgroundColor(0x80FFFFFF & MainApp.gc(R.color.cob)); //50% + cobSeries.setColor(MainApp.gc(R.color.cob)); cobSeries.setThickness(3); if (useForScale) @@ -350,6 +406,10 @@ public class GraphData { cobScale.setMultiplier(maxY * scale / maxCobValueFound); addSeries(cobSeries); + + DataPointWithLabelInterface[] minFailover = new DataPointWithLabelInterface[minFailoverActiveList.size()]; + minFailover = minFailoverActiveList.toArray(minFailover); + addSeries(new PointsWithLabelGraphSeries<>(minFailover)); } // scale in % of vertical size (like 0.3) @@ -369,7 +429,7 @@ public class GraphData { Scale devScale = new Scale(); for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { - AutosensData autosensData = IobCobCalculatorPlugin.getAutosensData(time); + AutosensData autosensData = IobCobCalculatorPlugin.getPlugin().getAutosensData(time); if (autosensData != null) { int color = Color.BLACK; // "=" if (autosensData.pastSensitivity.equals("C")) color = Color.GRAY; @@ -401,24 +461,24 @@ public class GraphData { // scale in % of vertical size (like 0.3) public void addRatio(long fromTime, long toTime, boolean useForScale, double scale) { - LineGraphSeries ratioSeries; - List ratioArray = new ArrayList<>(); + LineGraphSeries ratioSeries; + List ratioArray = new ArrayList<>(); Double maxRatioValueFound = 0d; Scale ratioScale = new Scale(-1d); for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { - AutosensData autosensData = IobCobCalculatorPlugin.getAutosensData(time); + AutosensData autosensData = IobCobCalculatorPlugin.getPlugin().getAutosensData(time); if (autosensData != null) { - ratioArray.add(new DataPoint(time, autosensData.autosensRatio)); + ratioArray.add(new ScaledDataPoint(time, autosensData.autosensRatio, ratioScale)); maxRatioValueFound = Math.max(maxRatioValueFound, Math.abs(autosensData.autosensRatio)); } } // RATIOS - DataPoint[] ratioData = new DataPoint[ratioArray.size()]; + ScaledDataPoint[] ratioData = new ScaledDataPoint[ratioArray.size()]; ratioData = ratioArray.toArray(ratioData); ratioSeries = new LineGraphSeries<>(ratioData); - ratioSeries.setColor(MainApp.sResources.getColor(R.color.ratio)); + ratioSeries.setColor(MainApp.gc(R.color.ratio)); ratioSeries.setThickness(3); if (useForScale) @@ -429,6 +489,50 @@ public class GraphData { addSeries(ratioSeries); } + // scale in % of vertical size (like 0.3) + public void addDeviationSlope(long fromTime, long toTime, boolean useForScale, double scale) { + LineGraphSeries dsMaxSeries; + LineGraphSeries dsMinSeries; + List dsMaxArray = new ArrayList<>(); + List dsMinArray = new ArrayList<>(); + Double maxFromMaxValueFound = 0d; + Double maxFromMinValueFound = 0d; + Scale dsMaxScale = new Scale(); + Scale dsMinScale = new Scale(); + + for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { + AutosensData autosensData = IobCobCalculatorPlugin.getPlugin().getAutosensData(time); + if (autosensData != null) { + dsMaxArray.add(new ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, dsMaxScale)); + dsMinArray.add(new ScaledDataPoint(time, autosensData.slopeFromMinDeviation, dsMinScale)); + maxFromMaxValueFound = Math.max(maxFromMaxValueFound, Math.abs(autosensData.slopeFromMaxDeviation)); + maxFromMinValueFound = Math.max(maxFromMinValueFound, Math.abs(autosensData.slopeFromMinDeviation)); + } + } + + // Slopes + ScaledDataPoint[] ratioMaxData = new ScaledDataPoint[dsMaxArray.size()]; + ratioMaxData = dsMaxArray.toArray(ratioMaxData); + dsMaxSeries = new LineGraphSeries<>(ratioMaxData); + dsMaxSeries.setColor(MainApp.gc(R.color.devslopepos)); + dsMaxSeries.setThickness(3); + + ScaledDataPoint[] ratioMinData = new ScaledDataPoint[dsMinArray.size()]; + ratioMinData = dsMinArray.toArray(ratioMinData); + dsMinSeries = new LineGraphSeries<>(ratioMinData); + dsMinSeries.setColor(MainApp.gc(R.color.devslopeneg)); + dsMinSeries.setThickness(3); + + if (useForScale) + maxY = Math.max(maxFromMaxValueFound, maxFromMinValueFound); + + dsMaxScale.setMultiplier(maxY * scale / maxFromMaxValueFound); + dsMinScale.setMultiplier(maxY * scale / maxFromMinValueFound); + + addSeries(dsMaxSeries); + addSeries(dsMinSeries); + } + // scale in % of vertical size (like 0.3) public void addNowLine(long now) { LineGraphSeries seriesNow; @@ -465,15 +569,15 @@ public class GraphData { public void performUpdate() { // clear old data graph.getSeries().clear(); - + // add precalculated series - for (Series s: series) { + for (Series s : series) { if (!s.isEmpty()) { s.onGraphViewAttached(graph); graph.getSeries().add(s); } } - + // draw it graph.onDataChanged(false, false); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphExtensions/DataPointWithLabelInterface.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphExtensions/DataPointWithLabelInterface.java index 20d478692a..3f6280431c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphExtensions/DataPointWithLabelInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphExtensions/DataPointWithLabelInterface.java @@ -55,4 +55,5 @@ public interface DataPointWithLabelInterface extends DataPointInterface{ PointsWithLabelGraphSeries.Shape getShape(); float getSize(); int getColor(); + int getSecondColor(); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphExtensions/PointsWithLabelGraphSeries.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphExtensions/PointsWithLabelGraphSeries.java index 26892372d5..8ac3be0ad8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphExtensions/PointsWithLabelGraphSeries.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphExtensions/PointsWithLabelGraphSeries.java @@ -3,42 +3,46 @@ package info.nightscout.androidaps.plugins.Overview.graphExtensions; /** * GraphView * Copyright (C) 2014 Jonas Gehring - * + *

* This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, * with the "Linking Exception", which can be found at the license.txt * file in this program. - * + *

* This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + *

* You should have received a copy of the GNU General Public License * with the "Linking Exception" along with this program; if not, * write to the author Jonas Gehring . + *

+ * Added by mike */ /** * Added by mike */ +import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Typeface; -import android.util.TypedValue; -// Added by Rumen for scalable text -import android.content.Context; -import info.nightscout.androidaps.MainApp; + import com.jjoe64.graphview.GraphView; import com.jjoe64.graphview.series.BaseSeries; import java.util.Iterator; +import info.nightscout.androidaps.MainApp; + +// Added by Rumen for scalable text + /** * Series that plots the data as points. * The points can be different shapes or a @@ -48,11 +52,11 @@ import java.util.Iterator; */ public class PointsWithLabelGraphSeries extends BaseSeries { // Default spSize - int spSize = 12; + int spSize = 14; // Convert the sp to pixels Context context = MainApp.instance().getApplicationContext(); float scaledTextSize = spSize * context.getResources().getDisplayMetrics().scaledDensity; - float scaledPxSize = context.getResources().getDisplayMetrics().scaledDensity * 1.5f; + float scaledPxSize = context.getResources().getDisplayMetrics().scaledDensity * 3f; /** * choose a predefined shape to render for @@ -60,17 +64,12 @@ public class PointsWithLabelGraphSeries e * You can also render a custom drawing via {@link com.jjoe64.graphview.series.PointsGraphSeries.CustomShape} */ public enum Shape { - /** - * draws a point / circle - */ - POINT, - - /** - * draws a triangle - */ + BG, + PREDICTION, TRIANGLE, RECTANGLE, BOLUS, + SMB, EXTENDEDBOLUS, PROFILE, MBG, @@ -79,7 +78,8 @@ public class PointsWithLabelGraphSeries e OPENAPSOFFLINE, EXERCISE, GENERAL, - GENERALWITHDURATION + GENERALWITHDURATION, + COBFAILOVER } /** @@ -153,7 +153,7 @@ public class PointsWithLabelGraphSeries e float scaleX = (float) (graphWidth / diffX); - int i=0; + int i = 0; while (values.hasNext()) { E value = values.next(); @@ -187,7 +187,7 @@ public class PointsWithLabelGraphSeries e } /* Fix a bug that continue to show the DOT after Y axis */ - if(x < 0) { + if (x < 0) { overdraw = true; } @@ -202,33 +202,52 @@ public class PointsWithLabelGraphSeries e // draw data point if (!overdraw) { - if (value.getShape() == Shape.POINT) { + if (value.getShape() == Shape.BG || value.getShape() == Shape.COBFAILOVER) { + mPaint.setStyle(Paint.Style.FILL); + mPaint.setStrokeWidth(0); + canvas.drawCircle(endX, endY, value.getSize() * scaledPxSize, mPaint); + } else if (value.getShape() == Shape.PREDICTION) { + mPaint.setColor(value.getColor()); + mPaint.setStyle(Paint.Style.FILL); mPaint.setStrokeWidth(0); canvas.drawCircle(endX, endY, scaledPxSize, mPaint); + mPaint.setColor(value.getSecondColor()); + mPaint.setStyle(Paint.Style.FILL); + mPaint.setStrokeWidth(0); + canvas.drawCircle(endX, endY, scaledPxSize / 3, mPaint); } else if (value.getShape() == Shape.RECTANGLE) { - canvas.drawRect(endX-scaledPxSize, endY-scaledPxSize, endX+scaledPxSize, endY+scaledPxSize, mPaint); + canvas.drawRect(endX - scaledPxSize, endY - scaledPxSize, endX + scaledPxSize, endY + scaledPxSize, mPaint); } else if (value.getShape() == Shape.TRIANGLE) { mPaint.setStrokeWidth(0); Point[] points = new Point[3]; - points[0] = new Point((int)endX, (int)(endY-scaledPxSize)); - points[1] = new Point((int)(endX+scaledPxSize), (int)(endY+scaledPxSize*0.67)); - points[2] = new Point((int)(endX-scaledPxSize), (int)(endY+scaledPxSize*0.67)); + points[0] = new Point((int) endX, (int) (endY - scaledPxSize)); + points[1] = new Point((int) (endX + scaledPxSize), (int) (endY + scaledPxSize * 0.67)); + points[2] = new Point((int) (endX - scaledPxSize), (int) (endY + scaledPxSize * 0.67)); drawArrows(points, canvas, mPaint); } else if (value.getShape() == Shape.BOLUS) { mPaint.setStrokeWidth(0); Point[] points = new Point[3]; - points[0] = new Point((int)endX, (int)(endY-scaledPxSize)); - points[1] = new Point((int)(endX+scaledPxSize), (int)(endY+scaledPxSize*0.67)); - points[2] = new Point((int)(endX-scaledPxSize), (int)(endY+scaledPxSize*0.67)); + points[0] = new Point((int) endX, (int) (endY - scaledPxSize)); + points[1] = new Point((int) (endX + scaledPxSize), (int) (endY + scaledPxSize * 0.67)); + points[2] = new Point((int) (endX - scaledPxSize), (int) (endY + scaledPxSize * 0.67)); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); drawArrows(points, canvas, mPaint); if (value.getLabel() != null) { drawLabel45(endX, endY, value, canvas); } + } else if (value.getShape() == Shape.SMB) { + mPaint.setStrokeWidth(2); + Point[] points = new Point[3]; + float size = value.getSize() * scaledPxSize; + points[0] = new Point((int) endX, (int) (endY - size)); + points[1] = new Point((int) (endX + size), (int) (endY + size * 0.67)); + points[2] = new Point((int) (endX - size), (int) (endY + size * 0.67)); + mPaint.setStyle(Paint.Style.FILL_AND_STROKE); + drawArrows(points, canvas, mPaint); } else if (value.getShape() == Shape.EXTENDEDBOLUS) { mPaint.setStrokeWidth(0); if (value.getLabel() != null) { - Rect bounds = new Rect((int)endX, (int)endY + 3, (int) (xpluslength), (int) endY + 8); + Rect bounds = new Rect((int) endX, (int) endY + 3, (int) (xpluslength), (int) endY + 8); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); canvas.drawRect(bounds, mPaint); mPaint.setTextSize((float) (scaledTextSize)); @@ -240,7 +259,7 @@ public class PointsWithLabelGraphSeries e mPaint.setStrokeWidth(0); if (value.getLabel() != null) { //mPaint.setTextSize((int) (scaledPxSize * 3)); - mPaint.setTextSize((float) (scaledTextSize*1.2)); + mPaint.setTextSize((float) (scaledTextSize * 1.2)); mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD)); Rect bounds = new Rect(); mPaint.getTextBounds(value.getLabel(), 0, value.getLabel().length(), bounds); @@ -341,7 +360,7 @@ public class PointsWithLabelGraphSeries e * @param paint paint object */ private void drawArrows(Point[] point, Canvas canvas, Paint paint) { - float [] points = new float[8]; + float[] points = new float[8]; points[0] = point[0].x; points[1] = point[0].y; points[2] = point[1].x; @@ -354,10 +373,10 @@ public class PointsWithLabelGraphSeries e canvas.save(); canvas.drawVertices(Canvas.VertexMode.TRIANGLES, 8, points, 0, null, 0, null, 0, null, 0, 0, paint); Path path = new Path(); - path.moveTo(point[0].x , point[0].y); - path.lineTo(point[1].x,point[1].y); - path.lineTo(point[2].x,point[2].y); - canvas.drawPath(path,paint); + path.moveTo(point[0].x, point[0].y); + path.lineTo(point[1].x, point[1].y); + path.lineTo(point[2].x, point[2].y); + canvas.drawPath(path, paint); canvas.restore(); } @@ -367,7 +386,7 @@ public class PointsWithLabelGraphSeries e float py = endY + scaledPxSize; canvas.save(); canvas.rotate(-45, px, py); - mPaint.setTextSize((float) (scaledTextSize*0.8)); + mPaint.setTextSize((float) (scaledTextSize * 0.8)); mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)); mPaint.setFakeBoldText(true); mPaint.setTextAlign(Paint.Align.RIGHT); @@ -379,7 +398,7 @@ public class PointsWithLabelGraphSeries e float py = endY - scaledPxSize; canvas.save(); canvas.rotate(-45, px, py); - mPaint.setTextSize((float) (scaledTextSize*0.8)); + mPaint.setTextSize((float) (scaledTextSize * 0.8)); mPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)); mPaint.setFakeBoldText(true); canvas.drawText(value.getLabel(), px + scaledPxSize, py, mPaint); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/notifications/Notification.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/notifications/Notification.java index 7f8b94cfa9..787545965c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/notifications/Notification.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/notifications/Notification.java @@ -42,10 +42,7 @@ public class Notification { public static final int APPROACHING_DAILY_LIMIT = 11; public static final int NSCLIENT_NO_WRITE_PERMISSION = 12; public static final int MISSING_SMS_PERMISSION = 13; - public static final int ISF_MISSING = 14; - public static final int IC_MISSING = 15; - public static final int BASAL_MISSING = 16; - public static final int TARGET_MISSING = 17; + public static final int NSANNOUNCEMENT = 18; public static final int NSALARM = 19; public static final int NSURGENTALARM = 20; @@ -53,12 +50,15 @@ public class Notification { public static final int TOAST_ALARM = 22; public static final int WRONGBASALSTEP = 23; public static final int WRONG_DRIVER = 24; + public static final int COMBO_PUMP_ALARM = 25; public static final int PUMP_UNREACHABLE = 26; public static final int BG_READINGS_MISSED = 27; public static final int UNSUPPORTED_FIRMWARE = 28; public static final int MINIMAL_BASAL_VALUE_REPLACED = 29; public static final int BASAL_PROFILE_NOT_ALIGNED_TO_HOURS = 30; public static final int ZERO_VALUE_IN_PROFILE = 31; + public static final int PROFILE_SWITCH_MISSING = 32; + public static final int NOT_ENG_MODE_OR_RELEASE = 33; public int id; public Date date; @@ -203,7 +203,7 @@ public class Notification { //log.debug("OpenAPS Alerts enabled: "+openAPSEnabledAlerts); // if no thresshold from Ns get it loccally if(threshold == null) threshold = SP.getDouble(R.string.key_nsalarm_staledatavalue,15D); - // No threshold of OpenAPS Alarm so using the one for BG + // No threshold of OpenAPS Alarm so using the one for BG // Added OpenAPSEnabledAlerts to alarm check if((bgReadingAgoMin > threshold && SP.getBoolean(R.string.key_nsalarm_staledata, false))||(bgReadingAgoMin > threshold && openAPSEnabledAlerts)){ return true; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/notifications/NotificationRecyclerViewAdapter.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/notifications/NotificationRecyclerViewAdapter.java new file mode 100644 index 0000000000..6087ae0210 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/notifications/NotificationRecyclerViewAdapter.java @@ -0,0 +1,107 @@ +package info.nightscout.androidaps.plugins.Overview.notifications; + +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.CardView; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Objects; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.NSClientInternal.broadcasts.BroadcastAckAlarm; +import info.nightscout.androidaps.plugins.Overview.OverviewPlugin; +import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.SP; + +public class NotificationRecyclerViewAdapter extends RecyclerView.Adapter { + private static Logger log = LoggerFactory.getLogger(NotificationRecyclerViewAdapter.class); + + private List notificationsList; + + public NotificationRecyclerViewAdapter(List notificationsList) { + this.notificationsList = notificationsList; + } + + @Override + public NotificationsViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { + View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.overview_notification_item, viewGroup, false); + return new NotificationsViewHolder(v); + } + + @Override + public void onBindViewHolder(NotificationsViewHolder holder, int position) { + Notification notification = notificationsList.get(position); + holder.dismiss.setTag(notification); + if (Objects.equals(notification.text, MainApp.gs(R.string.nsalarm_staledata))) + holder.dismiss.setText("snooze"); + holder.text.setText(notification.text); + holder.time.setText(DateUtil.timeString(notification.date)); + if (notification.level == Notification.URGENT) + holder.cv.setBackgroundColor(ContextCompat.getColor(MainApp.instance(), R.color.notificationUrgent)); + else if (notification.level == Notification.NORMAL) + holder.cv.setBackgroundColor(ContextCompat.getColor(MainApp.instance(), R.color.notificationNormal)); + else if (notification.level == Notification.LOW) + holder.cv.setBackgroundColor(ContextCompat.getColor(MainApp.instance(), R.color.notificationLow)); + else if (notification.level == Notification.INFO) + holder.cv.setBackgroundColor(ContextCompat.getColor(MainApp.instance(), R.color.notificationInfo)); + else if (notification.level == Notification.ANNOUNCEMENT) + holder.cv.setBackgroundColor(ContextCompat.getColor(MainApp.instance(), R.color.notificationAnnouncement)); + } + + @Override + public int getItemCount() { + return notificationsList.size(); + } + + @Override + public void onAttachedToRecyclerView(RecyclerView recyclerView) { + super.onAttachedToRecyclerView(recyclerView); + } + + static class NotificationsViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + CardView cv; + TextView time; + TextView text; + Button dismiss; + + NotificationsViewHolder(View itemView) { + super(itemView); + cv = (CardView) itemView.findViewById(R.id.notification_cardview); + time = (TextView) itemView.findViewById(R.id.notification_time); + text = (TextView) itemView.findViewById(R.id.notification_text); + dismiss = (Button) itemView.findViewById(R.id.notification_dismiss); + dismiss.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + Notification notification = (Notification) v.getTag(); + switch (v.getId()) { + case R.id.notification_dismiss: + MainApp.bus().post(new EventDismissNotification(notification.id)); + if (notification.nsAlarm != null) { + BroadcastAckAlarm.handleClearAlarm(notification.nsAlarm, MainApp.instance().getApplicationContext(), 60 * 60 * 1000L); + } + // Adding current time to snooze if we got staleData + log.debug("Notification text is: " + notification.text); + if (notification.text.equals(MainApp.sResources.getString(R.string.nsalarm_staledata))) { + NotificationStore nstore = OverviewPlugin.getPlugin().notificationStore; + long msToSnooze = SP.getInt("nsalarm_staledatavalue", 15) * 60 * 1000L; + log.debug("snooze nsalarm_staledatavalue in minutes is " + SP.getInt("nsalarm_staledatavalue", 15) + "\n in ms is: " + msToSnooze + " currentTimeMillis is: " + System.currentTimeMillis()); + nstore.snoozeTo(System.currentTimeMillis() + (SP.getInt("nsalarm_staledatavalue", 15) * 60 * 1000L)); + } + break; + } + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/notifications/NotificationStore.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/notifications/NotificationStore.java index 1ce92249f7..f0bb147982 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/notifications/NotificationStore.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/notifications/NotificationStore.java @@ -1,5 +1,7 @@ package info.nightscout.androidaps.plugins.Overview.notifications; +import android.annotation.SuppressLint; +import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.content.Intent; @@ -8,6 +10,7 @@ import android.graphics.BitmapFactory; import android.media.AudioAttributes; import android.media.RingtoneManager; import android.net.Uri; +import android.os.Build; import android.support.v4.app.NotificationCompat; import org.slf4j.Logger; @@ -21,8 +24,6 @@ import java.util.List; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.Services.AlarmSoundService; -import info.nightscout.androidaps.plugins.Wear.WearPlugin; -//Added by Rumen for snooze time import info.nightscout.utils.SP; /** @@ -30,11 +31,15 @@ import info.nightscout.utils.SP; */ public class NotificationStore { + + public static final String CHANNEL_ID = "AndroidAPS-Overview"; + private static Logger log = LoggerFactory.getLogger(NotificationStore.class); public List store = new ArrayList(); public long snoozedUntil = 0L; public NotificationStore() { + createNotificationChannel(); } public class NotificationComparator implements Comparator { @@ -44,17 +49,13 @@ public class NotificationStore { } } - public Notification get(int index) { - return store.get(index); - } - - public void add(Notification n) { + public synchronized boolean add(Notification n) { log.info("Notification received: " + n.text); - for (int i = 0; i < store.size(); i++) { - if (get(i).id == n.id) { - get(i).date = n.date; - get(i).validTo = n.validTo; - return; + for (Notification storeNotification : store) { + if (storeNotification.id == n.id) { + storeNotification.date = n.date; + storeNotification.validTo = n.validTo; + return false; } } store.add(n); @@ -70,36 +71,13 @@ public class NotificationStore { } Collections.sort(store, new NotificationComparator()); + return true; } - private void raiseSystemNotification(Notification n) { - Context context = MainApp.instance().getApplicationContext(); - NotificationManager mgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - Bitmap largeIcon = BitmapFactory.decodeResource(context.getResources(), R.mipmap.blueowl); - Uri sound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM); - NotificationCompat.Builder notificationBuilder = - new NotificationCompat.Builder(context) - .setSmallIcon(R.drawable.ic_notification) - .setLargeIcon(largeIcon) - .setContentText(n.text) - .setPriority(NotificationCompat.PRIORITY_MAX) - .setDeleteIntent(DismissNotificationService.deleteIntent(n.id)); - if (n.level == Notification.URGENT) { - notificationBuilder.setVibrate(new long[]{1000, 1000, 1000, 1000}) - .setContentTitle(MainApp.sResources.getString(R.string.urgent_alarm)) - .setSound(sound, AudioAttributes.USAGE_ALARM); - } else { - notificationBuilder.setVibrate(new long[]{0, 100, 50, 100, 50}) - .setContentTitle(MainApp.sResources.getString(R.string.info)) - ; - } - mgr.notify(n.id, notificationBuilder.build()); - } - - public boolean remove(int id) { + public synchronized boolean remove(int id) { for (int i = 0; i < store.size(); i++) { - if (get(i).id == id) { - if (get(i).soundId != null) { + if (store.get(i).id == id) { + if (store.get(i).soundId != null) { Intent alarm = new Intent(MainApp.instance().getApplicationContext(), AlarmSoundService.class); MainApp.instance().stopService(alarm); } @@ -110,9 +88,9 @@ public class NotificationStore { return false; } - public void removeExpired() { + public synchronized void removeExpired() { for (int i = 0; i < store.size(); i++) { - Notification n = get(i); + Notification n = store.get(i); if (n.validTo.getTime() != 0 && n.validTo.getTime() < System.currentTimeMillis()) { store.remove(i); i--; @@ -133,4 +111,41 @@ public class NotificationStore { log.debug("Snoozed to current time and added back notification!"); } } + + private void raiseSystemNotification(Notification n) { + Context context = MainApp.instance().getApplicationContext(); + NotificationManager mgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + Bitmap largeIcon = BitmapFactory.decodeResource(context.getResources(), R.mipmap.blueowl); + Uri sound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM); + NotificationCompat.Builder notificationBuilder = + new NotificationCompat.Builder(context, CHANNEL_ID) + .setSmallIcon(R.drawable.ic_notification) + .setLargeIcon(largeIcon) + .setContentText(n.text) + .setPriority(NotificationCompat.PRIORITY_MAX) + .setDeleteIntent(DismissNotificationService.deleteIntent(n.id)); + if (n.level == Notification.URGENT) { + notificationBuilder.setVibrate(new long[]{1000, 1000, 1000, 1000}) + .setContentTitle(MainApp.sResources.getString(R.string.urgent_alarm)) + .setSound(sound, AudioAttributes.USAGE_ALARM); + } else { + notificationBuilder.setVibrate(new long[]{0, 100, 50, 100, 50}) + .setContentTitle(MainApp.sResources.getString(R.string.info)) + ; + } + mgr.notify(n.id, notificationBuilder.build()); + } + + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + + NotificationManager mNotificationManager = + (NotificationManager) MainApp.instance().getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE); + @SuppressLint("WrongConstant") NotificationChannel channel = new NotificationChannel(CHANNEL_ID, + CHANNEL_ID, + NotificationManager.IMPORTANCE_HIGH); + mNotificationManager.createNotificationChannel(channel); + } + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Persistentnotification/PersistentNotificationPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Persistentnotification/PersistentNotificationPlugin.java index 558d01b94d..789c9df143 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Persistentnotification/PersistentNotificationPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Persistentnotification/PersistentNotificationPlugin.java @@ -1,13 +1,16 @@ package info.nightscout.androidaps.plugins.Persistentnotification; +import android.annotation.SuppressLint; +import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.support.v4.app.TaskStackBuilder; +import android.os.Build; import android.support.v4.app.NotificationCompat; +import android.support.v4.app.TaskStackBuilder; import com.squareup.otto.Subscribe; @@ -29,97 +32,69 @@ import info.nightscout.androidaps.events.EventRefreshOverview; import info.nightscout.androidaps.events.EventTempBasalChange; import info.nightscout.androidaps.events.EventTreatmentChange; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.DecimalFormatter; /** * Created by adrian on 23/12/16. */ -public class PersistentNotificationPlugin implements PluginBase { +public class PersistentNotificationPlugin extends PluginBase { + + public static final String CHANNEL_ID = "AndroidAPS-Ongoing"; private static final int ONGOING_NOTIFICATION_ID = 4711; - private boolean fragmentEnabled = true; private final Context ctx; public PersistentNotificationPlugin(Context ctx) { + super(new PluginDescription() + .mainType(PluginType.GENERAL) + .neverVisible(true) + .pluginName(R.string.ongoingnotificaction) + .enableByDefault(true) + ); this.ctx = ctx; } - @Override - public int getType() { - return GENERAL; + protected void onStart() { + MainApp.bus().register(this); + createNotificationChannel(); + updateNotification(); + super.onStart(); } - @Override - public String getFragmentClass() { - return null; - } + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - @Override - public String getName() { - return ctx.getString(R.string.ongoingnotificaction); - } - - @Override - public String getNameShort() { - // use long name as fallback (not visible in tabs) - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return false; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return false; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (getType() == type) { - this.fragmentEnabled = fragmentEnabled; - enableDisableNotification(fragmentEnabled); - checkBusRegistration(); - } - } - - private void enableDisableNotification(boolean fragmentEnabled) { - if (!fragmentEnabled) { NotificationManager mNotificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); - mNotificationManager.cancel(ONGOING_NOTIFICATION_ID); - } else { - updateNotification(); + @SuppressLint("WrongConstant") NotificationChannel channel = new NotificationChannel(CHANNEL_ID, + CHANNEL_ID, + NotificationManager.IMPORTANCE_HIGH); + mNotificationManager.createNotificationChannel(channel); } } + @Override + protected void onStop() { + MainApp.bus().unregister(this); + NotificationManager mNotificationManager = + (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); + mNotificationManager.cancel(ONGOING_NOTIFICATION_ID); + } + private void updateNotification() { - if (!fragmentEnabled) { + if (!isEnabled(PluginType.GENERAL)) { return; } String line1 = ctx.getString(R.string.noprofile); - if (MainApp.getConfigBuilder().getActiveProfileInterface() == null || MainApp.getConfigBuilder().getProfile() == null) + if (MainApp.getConfigBuilder().getActiveProfileInterface() == null || !MainApp.getConfigBuilder().isProfileValid("Notificiation")) return; String units = MainApp.getConfigBuilder().getProfileUnits(); @@ -139,16 +114,16 @@ public class PersistentNotificationPlugin implements PluginBase { } } - TemporaryBasal activeTemp = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); + TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); if (activeTemp != null) { line1 += " " + activeTemp.toStringShort(); } //IOB - MainApp.getConfigBuilder().updateTotalIOBTreatments(); - MainApp.getConfigBuilder().updateTotalIOBTempBasals(); - IobTotal bolusIob = MainApp.getConfigBuilder().getLastCalculationTreatments().round(); - IobTotal basalIob = MainApp.getConfigBuilder().getLastCalculationTempBasals().round(); + TreatmentsPlugin.getPlugin().updateTotalIOBTreatments(); + TreatmentsPlugin.getPlugin().updateTotalIOBTempBasals(); + IobTotal bolusIob = TreatmentsPlugin.getPlugin().getLastCalculationTreatments().round(); + IobTotal basalIob = TreatmentsPlugin.getPlugin().getLastCalculationTempBasals().round(); String line2 = ctx.getString(R.string.treatments_iob_label_string) + " " + DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob) + "U (" + ctx.getString(R.string.bolus) + ": " + DecimalFormatter.to2Decimal(bolusIob.iob) + "U " @@ -161,7 +136,7 @@ public class PersistentNotificationPlugin implements PluginBase { line3 += " - " + MainApp.getConfigBuilder().getProfileName(); - NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx); + NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx, CHANNEL_ID); builder.setOngoing(true); builder.setCategory(NotificationCompat.CATEGORY_STATUS); builder.setSmallIcon(R.drawable.ic_notification); @@ -190,32 +165,6 @@ public class PersistentNotificationPlugin implements PluginBase { } - private void checkBusRegistration() { - if (fragmentEnabled) { - try { - MainApp.bus().register(this); - } catch (IllegalArgumentException e) { - // already registered - } - } else { - try { - MainApp.bus().unregister(this); - } catch (IllegalArgumentException e) { - // already unregistered - } - } - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - //no visible fragment - } - - @Override - public int getPreferencesId() { - return -1; - } - private String deltastring(double deltaMGDL, double deltaMMOL, String units) { String deltastring = ""; if (deltaMGDL >= 0) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileCircadianPercentage/CircadianPercentageProfileFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileCircadianPercentage/CircadianPercentageProfileFragment.java deleted file mode 100644 index 74b400e1af..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileCircadianPercentage/CircadianPercentageProfileFragment.java +++ /dev/null @@ -1,546 +0,0 @@ -package info.nightscout.androidaps.plugins.ProfileCircadianPercentage; - - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.design.widget.Snackbar; -import android.support.v4.app.DialogFragment; -import android.support.v4.content.ContextCompat; -import android.text.Editable; -import android.text.Html; -import android.text.TextWatcher; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.inputmethod.InputMethodManager; -import android.widget.Button; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.RadioButton; -import android.widget.TextView; - -import com.andreabaccega.widget.FormEditText; -import com.squareup.otto.Subscribe; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.events.EventInitializationChanged; -import info.nightscout.androidaps.events.EventProfileSwitchChange; -import info.nightscout.androidaps.plugins.Careportal.CareportalFragment; -import info.nightscout.androidaps.plugins.Careportal.Dialogs.NewNSTreatmentDialog; -import info.nightscout.androidaps.plugins.Careportal.OptionsToShow; -import info.nightscout.androidaps.plugins.Common.SubscriberFragment; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; -import info.nightscout.utils.DecimalFormatter; -import info.nightscout.utils.SafeParse; - -public class CircadianPercentageProfileFragment extends SubscriberFragment { - private static Logger log = LoggerFactory.getLogger(CircadianPercentageProfileFragment.class); - - private static CircadianPercentageProfilePlugin circadianPercentageProfilePlugin = new CircadianPercentageProfilePlugin(); - private Object snackbarCaller; - - public static CircadianPercentageProfilePlugin getPlugin() { - return circadianPercentageProfilePlugin; - } - - FormEditText diaView; - RadioButton mgdlView; - RadioButton mmolView; - FormEditText targetlowView; - FormEditText targethighView; - FormEditText percentageView; - FormEditText timeshiftView; - TextView profileView; - TextView baseprofileIC; - TextView baseprofileBasal; - LinearLayout baseprofileBasalLayout; - TextView baseprofileISF; - Button profileswitchButton; - ImageView percentageIcon; - ImageView timeIcon; - ImageView basaleditIcon; - ImageView iceditIcon; - ImageView isfeditIcon; - BasalEditDialog basalEditDialog; - FrameLayout fl; - Snackbar mSnackBar; - - static Boolean percentageViewHint = true; - static Boolean timeshiftViewHint = true; - - TextWatcher textWatch = new TextWatcher() { - - @Override - public void afterTextChanged(Editable s) { - } - - @Override - public void beforeTextChanged(CharSequence s, int start, - int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, - int before, int count) { - - if (percentageView.testValidity()) { - if (SafeParse.stringToInt(percentageView.getText().toString()) == 0) { - circadianPercentageProfilePlugin.percentage = 100; - } else { - circadianPercentageProfilePlugin.percentage = SafeParse.stringToInt(percentageView.getText().toString()); - } - updateProfileInfo(); - } - if (timeshiftView.testValidity()) { - circadianPercentageProfilePlugin.timeshift = SafeParse.stringToInt(timeshiftView.getText().toString()); - updateProfileInfo(); - } - if (diaView.testValidity()) { - circadianPercentageProfilePlugin.dia = SafeParse.stringToDouble(diaView.getText().toString()); - updateProfileInfo(); - } - if (targethighView.testValidity()) { - circadianPercentageProfilePlugin.targetLow = SafeParse.stringToDouble(targetlowView.getText().toString()); - updateProfileInfo(); - } - if (targetlowView.testValidity()) { - circadianPercentageProfilePlugin.targetHigh = SafeParse.stringToDouble(targethighView.getText().toString()); - updateProfileInfo(); - } - circadianPercentageProfilePlugin.storeSettings(); - updateProfileInfo(); - } - }; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - showDeprecatedDialog(); - - View layout = inflater.inflate(R.layout.circadianpercentageprofile_fragment, container, false); - fl = (FrameLayout) layout.findViewById(R.id.circadianpercentageprofile_framelayout); - fl.requestFocusFromTouch(); - diaView = (FormEditText) layout.findViewById(R.id.circadianpercentageprofile_dia); - mgdlView = (RadioButton) layout.findViewById(R.id.circadianpercentageprofile_mgdl); - mmolView = (RadioButton) layout.findViewById(R.id.circadianpercentageprofile_mmol); - targetlowView = (FormEditText) layout.findViewById(R.id.circadianpercentageprofile_targetlow); - targethighView = (FormEditText) layout.findViewById(R.id.circadianpercentageprofile_targethigh); - percentageView = (FormEditText) layout.findViewById(R.id.circadianpercentageprofile_percentage); - timeshiftView = (FormEditText) layout.findViewById(R.id.circadianpercentageprofile_timeshift); - profileView = (TextView) layout.findViewById(R.id.circadianpercentageprofile_profileview); - baseprofileBasal = (TextView) layout.findViewById(R.id.circadianpercentageprofile_baseprofilebasal); - baseprofileBasalLayout = (LinearLayout) layout.findViewById(R.id.circadianpercentageprofile_baseprofilebasal_layout); - baseprofileIC = (TextView) layout.findViewById(R.id.circadianpercentageprofile_baseprofileic); - baseprofileISF = (TextView) layout.findViewById(R.id.circadianpercentageprofile_baseprofileisf); - percentageIcon = (ImageView) layout.findViewById(R.id.circadianpercentageprofile_percentageicon); - timeIcon = (ImageView) layout.findViewById(R.id.circadianpercentageprofile_timeicon); - profileswitchButton = (Button) layout.findViewById(R.id.circadianpercentageprofile_profileswitch); - - basaleditIcon = (ImageView) layout.findViewById(R.id.circadianpercentageprofile_basaledit); - iceditIcon = (ImageView) layout.findViewById(R.id.circadianpercentageprofile_icedit); - isfeditIcon = (ImageView) layout.findViewById(R.id.circadianpercentageprofile_isfedit); - - if (!ConfigBuilderPlugin.getActivePump().getPumpDescription().isTempBasalCapable) { - layout.findViewById(R.id.circadianpercentageprofile_baseprofilebasal_layout).setVisibility(View.GONE); - } - - mgdlView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - circadianPercentageProfilePlugin.mgdl = mgdlView.isChecked(); - circadianPercentageProfilePlugin.mmol = !circadianPercentageProfilePlugin.mgdl; - mmolView.setChecked(circadianPercentageProfilePlugin.mmol); - circadianPercentageProfilePlugin.storeSettings(); - updateProfileInfo(); - } - }); - mmolView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - circadianPercentageProfilePlugin.mmol = mmolView.isChecked(); - circadianPercentageProfilePlugin.mgdl = !circadianPercentageProfilePlugin.mmol; - mgdlView.setChecked(circadianPercentageProfilePlugin.mgdl); - circadianPercentageProfilePlugin.storeSettings(); - updateProfileInfo(); - } - }); - - profileswitchButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - NewNSTreatmentDialog newDialog = new NewNSTreatmentDialog(); - final OptionsToShow profileswitch = CareportalFragment.PROFILESWITCHDIRECT; - profileswitch.executeProfileSwitch = true; - newDialog.setOptions(profileswitch, R.string.careportal_profileswitch); - newDialog.show(getFragmentManager(), "NewNSTreatmentDialog"); - } - }); - - timeIcon.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - timeshiftView.requestFocusFromTouch(); - timeshiftView.setSelection(timeshiftView.getText().length()); - ((InputMethodManager) getContext() - .getSystemService(Context.INPUT_METHOD_SERVICE)).showSoftInput(timeshiftView, InputMethodManager.SHOW_IMPLICIT); - } - }); - - percentageIcon.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - percentageView.requestFocusFromTouch(); - percentageView.setSelection(percentageView.getText().length()); - ((InputMethodManager) getContext() - .getSystemService(Context.INPUT_METHOD_SERVICE)).showSoftInput(percentageView, InputMethodManager.SHOW_IMPLICIT); - } - }); - - basaleditIcon.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - basalEditDialog = new BasalEditDialog(); - basalEditDialog.setup(getPlugin().basebasal, getString(R.string.edit_base_basal), CircadianPercentageProfileFragment.this); - basalEditDialog.show(getFragmentManager(), getString(R.string.edit_base_basal)); - } - }); - - isfeditIcon.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - basalEditDialog = new BasalEditDialog(); - basalEditDialog.setup(getPlugin().baseisf, getString(R.string.edit_base_isf), CircadianPercentageProfileFragment.this); - basalEditDialog.show(getFragmentManager(), getString(R.string.edit_base_isf)); - } - }); - - iceditIcon.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - basalEditDialog = new BasalEditDialog(); - basalEditDialog.setup(getPlugin().baseic, getString(R.string.edit_base_ic), CircadianPercentageProfileFragment.this); - basalEditDialog.show(getFragmentManager(), getString(R.string.edit_base_ic)); - } - }); - - /*timeshiftView.setOnFocusChangeListener(new View.OnFocusChangeListener() { - - @Override - public void onFocusChange(View view, boolean hasFocus) { - if (!hasFocus) { - if(mSnackBar!=null && snackbarCaller == timeshiftView){ - mSnackBar.dismiss(); - } - timeshiftView.clearFocus(); - fl.requestFocusFromTouch(); - } - else { - if (timeshiftViewHint) { - customSnackbar(view, getString(R.string.timeshift_hint), timeshiftView); - } - } - } - }); - - percentageView.setOnFocusChangeListener(new View.OnFocusChangeListener() { - - @Override - public void onFocusChange(View view, boolean hasFocus) { - if (!hasFocus) { - if(mSnackBar!=null && snackbarCaller == percentageView){ - mSnackBar.dismiss(); - } - percentageView.clearFocus(); - fl.requestFocusFromTouch(); - } - else { - if (percentageViewHint) { - customSnackbar(view, getString(R.string.percentagefactor_hint), percentageView); - } - } - } - });*/ - - diaView.setOnFocusChangeListener(new View.OnFocusChangeListener() { - - @Override - public void onFocusChange(View view, boolean hasFocus) { - if (!hasFocus) { - diaView.clearFocus(); - fl.requestFocusFromTouch(); - } - } - }); - - targethighView.setOnFocusChangeListener(new View.OnFocusChangeListener() { - - @Override - public void onFocusChange(View view, boolean hasFocus) { - if (!hasFocus) { - targethighView.clearFocus(); - fl.requestFocusFromTouch(); - } - } - }); - - targetlowView.setOnFocusChangeListener(new View.OnFocusChangeListener() { - - @Override - public void onFocusChange(View view, boolean hasFocus) { - if (!hasFocus) { - targetlowView.clearFocus(); - fl.requestFocusFromTouch(); - } - } - }); - - - diaView.addTextChangedListener(textWatch); - targetlowView.addTextChangedListener(textWatch); - targethighView.addTextChangedListener(textWatch); - percentageView.addTextChangedListener(textWatch); - timeshiftView.addTextChangedListener(textWatch); - - updateGUI(); - - onStatusEvent(new EventInitializationChanged()); - - return layout; - } - - private void showDeprecatedDialog() { - AlertDialog.Builder adb = new AlertDialog.Builder(getContext()); - adb.setTitle("DEPRECATED! Please migrate!"); - adb.setMessage("CircadianPercentageProfile has been deprecated. " + - "It is recommended to migrate to LocalProfile.\n\n" + - "Good news: You won't lose any functionality! Percentage and Timeshift have been ported to the ProfileSwitch :) \n\n " + - "How to migrate:\n" + - "1) Press MIGRATE, the system will automatically fill the LocalProfile for you.\n" + - "2) Switch to LocalProfile in the ConfigBuilder\n" + - "3) CHECK that all settings are correct in the LocalProfile!!!"); - adb.setIcon(android.R.drawable.ic_dialog_alert); - adb.setPositiveButton("MIGRATE", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - CircadianPercentageProfilePlugin.migrateToLP(); - } - }); - adb.setNegativeButton("Cancel", null); - adb.show(); - } - - public void updateGUI() { - updateProfileInfo(); - - diaView.removeTextChangedListener(textWatch); - targetlowView.removeTextChangedListener(textWatch); - targethighView.removeTextChangedListener(textWatch); - percentageView.removeTextChangedListener(textWatch); - timeshiftView.removeTextChangedListener(textWatch); - - mgdlView.setChecked(circadianPercentageProfilePlugin.mgdl); - mmolView.setChecked(circadianPercentageProfilePlugin.mmol); - diaView.setText(circadianPercentageProfilePlugin.dia.toString()); - targetlowView.setText(circadianPercentageProfilePlugin.targetLow.toString()); - targethighView.setText(circadianPercentageProfilePlugin.targetHigh.toString()); - percentageView.setText("" + circadianPercentageProfilePlugin.percentage); - timeshiftView.setText("" + circadianPercentageProfilePlugin.timeshift); - - - diaView.addTextChangedListener(textWatch); - targetlowView.addTextChangedListener(textWatch); - targethighView.addTextChangedListener(textWatch); - percentageView.addTextChangedListener(textWatch); - timeshiftView.addTextChangedListener(textWatch); - - } - - private void customSnackbar(View view, final String Msg, Object snackbarCaller) { - if (mSnackBar != null) mSnackBar.dismiss(); - - this.snackbarCaller = snackbarCaller; - if (timeshiftViewHint || percentageViewHint) { - //noinspection WrongConstant - mSnackBar = Snackbar.make(view, Msg, 7000) - .setActionTextColor(ContextCompat.getColor(MainApp.instance(), R.color.notificationInfo)) - .setAction(getString(R.string.dont_show_again), new View.OnClickListener() { - @Override - public void onClick(View v) { - if (Msg.equals(getString(R.string.percentagefactor_hint))) { - percentageViewHint = false; - } else if (Msg.equals(getString(R.string.timeshift_hint))) { - timeshiftViewHint = false; - } - } - }); - view = mSnackBar.getView(); - FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) view.getLayoutParams(); - params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; - view.setLayoutParams(params); - view.setBackgroundColor(ContextCompat.getColor(MainApp.instance(), R.color.cardview_dark_background)); - TextView mainTextView = (TextView) (view).findViewById(android.support.design.R.id.snackbar_text); - mainTextView.setTextColor(ContextCompat.getColor(MainApp.instance(), R.color.mdtp_white)); - mSnackBar.show(); - } - } - - private void updateProfileInfo() { - StringBuilder sb = new StringBuilder(); - sb.append("

"); - sb.append(getString(R.string.nsprofileview_activeprofile_label)); - sb.append("

"); - sb.append("

"); - sb.append(getString(R.string.nsprofileview_basal_label)); - sb.append(" ( ∑"); - sb.append(DecimalFormatter.to2Decimal(circadianPercentageProfilePlugin.percentageBasalSum())); - sb.append("U )"); - sb.append("

" + circadianPercentageProfilePlugin.basalString()); - sb.append("

"); - sb.append(getString(R.string.nsprofileview_ic_label)); - sb.append("

" + circadianPercentageProfilePlugin.icString()); - sb.append("

"); - sb.append(getString(R.string.nsprofileview_isf_label)); - sb.append("

" + circadianPercentageProfilePlugin.isfString()); - profileView.setText(Html.fromHtml(sb.toString())); - - baseprofileBasal.setText(Html.fromHtml("

" + getString(R.string.base_profile_label) + " ( ∑" + DecimalFormatter.to2Decimal(circadianPercentageProfilePlugin.baseBasalSum()) + "U )

" + - "

" + getString(R.string.nsprofileview_basal_label) + "

" + circadianPercentageProfilePlugin.baseBasalString())); - baseprofileIC.setText(Html.fromHtml("

" + getString(R.string.nsprofileview_ic_label) + "

" + circadianPercentageProfilePlugin.baseIcString())); - baseprofileISF.setText(Html.fromHtml("

" + getString(R.string.nsprofileview_isf_label) + "

" + circadianPercentageProfilePlugin.baseIsfString())); - } - - @Override - public void onStop() { - super.onStop(); - if (basalEditDialog != null && basalEditDialog.isVisible()) { - basalEditDialog.dismiss(); - } - basalEditDialog = null; - fl.requestFocusFromTouch(); - } - - public static class BasalEditDialog extends DialogFragment { - - private double[] values; - private String title; - private CircadianPercentageProfileFragment fragment; - - public void setup(double[] values, String title, CircadianPercentageProfileFragment fragment) { - this.values = values; - this.title = title; - this.fragment = fragment; - } - - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - getDialog().setTitle(title); - View view = inflater.inflate(R.layout.circadianpercentageprofile_editbasal_dialog, container, false); - LinearLayout list = (LinearLayout) view.findViewById(R.id.circadianpp_editbasal_listlayout); - final EditText[] editTexts = new EditText[24]; - for (int i = 0; i < 24; i++) { - View childview = inflater.inflate(R.layout.circadianpercentageprofile_listelement, container, false); - ((TextView) childview.findViewById(R.id.basal_time_elem)).setText((i < 10 ? "0" : "") + i + ":00: "); - - ImageView copyprevbutton = (ImageView) childview.findViewById(R.id.basal_copyprev_elem); - - if (i == 0) { - copyprevbutton.setVisibility(View.INVISIBLE); - } else { - final int j = i; //needs to be final to be passed to inner class. - copyprevbutton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - editTexts[j].setText(editTexts[j - 1].getText()); - } - }); - } - - editTexts[i] = ((EditText) childview.findViewById(R.id.basal_edittext_elem)); - editTexts[i].setText(DecimalFormatter.to2Decimal(values[i])); - list.addView(childview); - } - getDialog().setCancelable(true); - - view.findViewById(R.id.ok_button).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - for (int i = 0; i < 24; i++) { - if (editTexts[i].getText().length() != 0) { - values[i] = SafeParse.stringToDouble(editTexts[i].getText().toString()); - } - } - fragment.updateProfileInfo(); - getPlugin().storeSettings(); - dismiss(); - } - }); - - view.findViewById(R.id.cancel_action).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - dismiss(); - } - }); - - return view; - } - } - - @Override - public void onPause() { - super.onPause(); - - if (basalEditDialog != null && basalEditDialog.isVisible()) { - basalEditDialog.dismiss(); - } - basalEditDialog = null; - - fl.requestFocusFromTouch(); - } - - @Override - public void onResume() { - super.onResume(); - onStatusEvent(new EventInitializationChanged()); - fl.requestFocusFromTouch(); - } - - @Subscribe - public void onStatusEvent(final EventInitializationChanged e) { - Activity activity = getActivity(); - if (activity != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - if (!ConfigBuilderPlugin.getActivePump().isInitialized() || ConfigBuilderPlugin.getActivePump().isSuspended()) { - profileswitchButton.setVisibility(View.GONE); - } else { - profileswitchButton.setVisibility(View.VISIBLE); - } - } - }); - } - - @Subscribe - public void onStatusEvent(final EventProfileSwitchChange e) { - Activity activity = getActivity(); - if (activity != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - updateGUI(); - } - }); - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileCircadianPercentage/CircadianPercentageProfilePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileCircadianPercentage/CircadianPercentageProfilePlugin.java deleted file mode 100644 index ec9c6d1e40..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileCircadianPercentage/CircadianPercentageProfilePlugin.java +++ /dev/null @@ -1,455 +0,0 @@ -package info.nightscout.androidaps.plugins.ProfileCircadianPercentage; - -import android.content.SharedPreferences; -import android.preference.PreferenceManager; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.text.DecimalFormat; - -import info.nightscout.androidaps.Config; -import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.Profile; -import info.nightscout.androidaps.data.ProfileStore; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.ProfileInterface; -import info.nightscout.androidaps.plugins.Careportal.Dialogs.NewNSTreatmentDialog; -import info.nightscout.androidaps.plugins.ProfileLocal.LocalProfilePlugin; -import info.nightscout.utils.DecimalFormatter; -import info.nightscout.utils.NSUpload; -import info.nightscout.utils.SP; -import info.nightscout.utils.SafeParse; -import info.nightscout.utils.ToastUtils; - -/** - * Created by Adrian on 12.11.2016. - * Based on SimpleProfile created by mike on 05.08.2016. - */ -public class CircadianPercentageProfilePlugin implements PluginBase, ProfileInterface { - public static final String SETTINGS_PREFIX = "CircadianPercentageProfile"; - private static Logger log = LoggerFactory.getLogger(CircadianPercentageProfilePlugin.class); - - private boolean fragmentEnabled = false; - private boolean fragmentVisible = false; - - private static ProfileStore convertedProfile = null; - private static String convertedProfileName = null; - - boolean mgdl; - boolean mmol; - Double dia; - Double targetLow; - Double targetHigh; - public int percentage; - public int timeshift; - double[] basebasal = new double[]{1d, 1d, 1d, 1d, 1d, 1d, 1d, 1d, 1d, 1d, 1d, 1d, 1d, 1d, 1d, 1d, 1d, 1d, 1d, 1d, 1d, 1d, 1d, 1d}; - double[] baseisf = new double[]{35d, 35d, 35d, 35d, 35d, 35d, 35d, 35d, 35d, 35d, 35d, 35d, 35d, 35d, 35d, 35d, 35d, 35d, 35d, 35d, 35d, 35d, 35d, 35d}; - double[] baseic = new double[]{4d, 4d, 4d, 4d, 4d, 4d, 4d, 4d, 4d, 4d, 4d, 4d, 4d, 4d, 4d, 4d, 4d, 4d, 4d, 4d, 4d, 4d, 4d, 4d}; - - public CircadianPercentageProfilePlugin() { - loadSettings(); - } - - @Override - public String getFragmentClass() { - return CircadianPercentageProfileFragment.class.getName(); - } - - @Override - public int getType() { - return PluginBase.PROFILE; - } - - @Override - public String getName() { - return MainApp.instance().getString(R.string.circadian_percentage_profile); - } - - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.circadian_percentage_profile_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == PROFILE && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == PROFILE && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == PROFILE) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == PROFILE) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return -1; - } - - void storeSettings() { - if (Config.logPrefsChange) - log.debug("Storing settings"); - SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(MainApp.instance().getApplicationContext()); - SharedPreferences.Editor editor = settings.edit(); - editor.putBoolean(SETTINGS_PREFIX + "mmol", mmol); - editor.putBoolean(SETTINGS_PREFIX + "mgdl", mgdl); - editor.putString(SETTINGS_PREFIX + "dia", dia.toString()); - editor.putString(SETTINGS_PREFIX + "targetlow", targetLow.toString()); - editor.putString(SETTINGS_PREFIX + "targethigh", targetHigh.toString()); - editor.putString(SETTINGS_PREFIX + "timeshift", timeshift + ""); - editor.putString(SETTINGS_PREFIX + "percentage", percentage + ""); - - - for (int i = 0; i < 24; i++) { - editor.putString(SETTINGS_PREFIX + "basebasal" + i, DecimalFormatter.to2Decimal(basebasal[i])); - editor.putString(SETTINGS_PREFIX + "baseisf" + i, DecimalFormatter.to2Decimal(baseisf[i])); - editor.putString(SETTINGS_PREFIX + "baseic" + i, DecimalFormatter.to2Decimal(baseic[i])); - } - editor.commit(); - createConvertedProfile(); - } - - void loadSettings() { - if (Config.logPrefsChange) - log.debug("Loading stored settings"); - - mgdl = SP.getBoolean(SETTINGS_PREFIX + "mgdl", true); - mmol = SP.getBoolean(SETTINGS_PREFIX + "mmol", false); - dia = SP.getDouble(SETTINGS_PREFIX + "dia", Constants.defaultDIA); - targetLow = SP.getDouble(SETTINGS_PREFIX + "targetlow", 80d); - targetHigh = SP.getDouble(SETTINGS_PREFIX + "targethigh", 120d); - percentage = SP.getInt(SETTINGS_PREFIX + "percentage", 100); - timeshift = SP.getInt(SETTINGS_PREFIX + "timeshift", 0); - - for (int i = 0; i < 24; i++) { - basebasal[i] = SP.getDouble(SETTINGS_PREFIX + "basebasal" + i, basebasal[i]); - baseic[i] = SP.getDouble(SETTINGS_PREFIX + "baseic" + i, baseic[i]); - baseisf[i] = SP.getDouble(SETTINGS_PREFIX + "baseisf" + i, baseisf[i]); - } - } - - public String externallySetParameters(int timeshift, int percentage) { - - String msg = ""; - - if (!fragmentEnabled) { - msg += "NO CPP!" + "\n"; - } - - //check for validity - if (percentage < Constants.CPP_MIN_PERCENTAGE || percentage > Constants.CPP_MAX_PERCENTAGE) { - msg += String.format(MainApp.sResources.getString(R.string.openapsma_valueoutofrange), "Profile-Percentage") + "\n"; - } - if (timeshift < 0 || timeshift > 23) { - msg += String.format(MainApp.sResources.getString(R.string.openapsma_valueoutofrange), "Profile-Timeshift") + "\n"; - } - final Profile profile = MainApp.getConfigBuilder().getProfile(); - - if (profile == null || profile.getBasal() == null) { - msg += MainApp.sResources.getString(R.string.cpp_notloadedplugins) + "\n"; - } - if (!"".equals(msg)) { - msg += MainApp.sResources.getString(R.string.cpp_valuesnotstored); - return msg; - } - - //store profile - this.timeshift = timeshift; - this.percentage = percentage; - storeSettings(); - - - //send profile to pumpe - new NewNSTreatmentDialog(); //init - NewNSTreatmentDialog.doProfileSwitch(this.getProfile(), this.getProfileName(), 0, percentage, timeshift); - - //return formatted string - /*msg += "%: " + this.percentage + " h: +" + this.timeshift; - msg += "\n"; - msg += "\nBasal:\n" + basalString() + "\n"; - msg += "\nISF:\n" + isfString() + "\n"; - msg += "\nIC:\n" + isfString() + "\n";*/ - - return msg; - } - - public static void migrateToLP() { - SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(MainApp.instance().getApplicationContext()); - SharedPreferences.Editor editor = settings.edit(); - editor.putBoolean("LocalProfile" + "mmol", SP.getBoolean(SETTINGS_PREFIX + "mmol", false)); - editor.putBoolean("LocalProfile" + "mgdl", SP.getBoolean(SETTINGS_PREFIX + "mgdl", true)); - editor.putString("LocalProfile" + "dia", "" + SP.getDouble(SETTINGS_PREFIX + "dia", Constants.defaultDIA)); - editor.putString("LocalProfile" + "ic", getLPic()); - editor.putString("LocalProfile" + "isf", getLPisf()); - editor.putString("LocalProfile" + "basal", getLPbasal()); - try { - JSONArray targetLow = new JSONArray().put(new JSONObject().put("time", "00:00").put("timeAsSeconds", 0).put("value", SP.getDouble(SETTINGS_PREFIX + "targetlow", 120d))); - JSONArray targetHigh = new JSONArray().put(new JSONObject().put("time", "00:00").put("timeAsSeconds", 0).put("value", SP.getDouble(SETTINGS_PREFIX + "targethigh", 120d))); - editor.putString("LocalProfile" + "targetlow", targetLow.toString()); - editor.putString("LocalProfile" + "targethigh", targetHigh.toString()); - } catch (JSONException e) { - e.printStackTrace(); - } - editor.commit(); - LocalProfilePlugin lp = MainApp.getSpecificPlugin(LocalProfilePlugin.class); - lp.loadSettings(); - - /* TODO: remove Settings and switch to LP later on - * For now only nag the user every time (s)he opens the CPP fragment and offer to migrate. - * Keep settings for now in order to allow the user to check that the migration went well. - */ - //removeSettings(); - - } - - public static String getLPisf() { - return getLPConversion("baseisf", 35d); - } - - public static String getLPic() { - return getLPConversion("baseic", 4); - } - - public static String getLPbasal() { - return getLPConversion("basebasal", 1); - } - - public static String getLPConversion(String type, double defaultValue) { - try { - JSONArray jsonArray = new JSONArray(); - double last = -1d; - - for (int i = 0; i < 24; i++) { - double value = SP.getDouble(SETTINGS_PREFIX + type + i, defaultValue); - String time; - DecimalFormat df = new DecimalFormat("00"); - time = df.format(i) + ":00"; - if (last != value) { - jsonArray.put(new JSONObject().put("time", time).put("timeAsSeconds", i * 60 * 60).put("value", value)); - } - last = value; - } - return jsonArray.toString(); - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - return LocalProfilePlugin.DEFAULTARRAY; - } - - static void removeSettings() { - if (Config.logPrefsChange) - log.debug("Removing settings"); - SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(MainApp.instance().getApplicationContext()); - SharedPreferences.Editor editor = settings.edit(); - editor.remove(SETTINGS_PREFIX + "mmol"); - editor.remove(SETTINGS_PREFIX + "mgdl"); - editor.remove(SETTINGS_PREFIX + "dia"); - editor.remove(SETTINGS_PREFIX + "targetlow"); - editor.remove(SETTINGS_PREFIX + "targethigh"); - editor.remove(SETTINGS_PREFIX + "timeshift"); - editor.remove(SETTINGS_PREFIX + "percentage"); - - - for (int i = 0; i < 24; i++) { - editor.remove(SETTINGS_PREFIX + "basebasal"); - editor.remove(SETTINGS_PREFIX + "baseisf" + i); - editor.remove(SETTINGS_PREFIX + "baseic" + i); - } - editor.commit(); - } - - private void createConvertedProfile() { - JSONObject json = new JSONObject(); - JSONObject store = new JSONObject(); - JSONObject profile = new JSONObject(); - - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(DecimalFormatter.to2Decimal(sum(basebasal))); - stringBuilder.append("U@"); - stringBuilder.append(percentage); - stringBuilder.append("%>"); - stringBuilder.append(timeshift); - stringBuilder.append("h"); - String profileName = stringBuilder.toString(); - - try { - json.put("defaultProfile", profileName); - json.put("store", store); - profile.put("dia", dia); - - int offset = -(timeshift % 24) + 24; - - JSONArray icArray = new JSONArray(); - JSONArray isfArray = new JSONArray(); - JSONArray basalArray = new JSONArray(); - for (int i = 0; i < 24; i++) { - String time; - DecimalFormat df = new DecimalFormat("00"); - time = df.format(i) + ":00"; - icArray.put(new JSONObject().put("time", time).put("timeAsSeconds", i * 60 * 60).put("value", baseic[(offset + i) % 24] * 100d / percentage)); - isfArray.put(new JSONObject().put("time", time).put("timeAsSeconds", i * 60 * 60).put("value", baseisf[(offset + i) % 24] * 100d / percentage)); - basalArray.put(new JSONObject().put("time", time).put("timeAsSeconds", i * 60 * 60).put("value", basebasal[(offset + i) % 24] * percentage / 100d)); - } - profile.put("carbratio", icArray); - profile.put("sens", isfArray); - profile.put("basal", basalArray); - - - profile.put("target_low", new JSONArray().put(new JSONObject().put("time", "00:00").put("timeAsSeconds", 0).put("value", targetLow))); - profile.put("target_high", new JSONArray().put(new JSONObject().put("time", "00:00").put("timeAsSeconds", 0).put("value", targetHigh))); - profile.put("units", mgdl ? Constants.MGDL : Constants.MMOL); - store.put(profileName, profile); - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - convertedProfile = new ProfileStore(json); - convertedProfileName = profileName; - } - - @Override - public ProfileStore getProfile() { - if (convertedProfile == null) - createConvertedProfile(); - - performLimitCheck(); - return convertedProfile; - } - - @Override - public String getUnits() { - return mgdl ? Constants.MGDL : Constants.MMOL; - } - - @Override - public String getProfileName() { - if (convertedProfile == null) - createConvertedProfile(); - - performLimitCheck(); - return convertedProfileName; - } - - private void performLimitCheck() { - if (percentage < Constants.CPP_MIN_PERCENTAGE || percentage > Constants.CPP_MAX_PERCENTAGE) { - String msg = String.format(MainApp.sResources.getString(R.string.openapsma_valueoutofrange), "Profile-Percentage"); - log.error(msg); - NSUpload.uploadError(msg); - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), msg, R.raw.error); - percentage = Math.max(percentage, Constants.CPP_MIN_PERCENTAGE); - percentage = Math.min(percentage, Constants.CPP_MAX_PERCENTAGE); - } - } - - String basalString() { - return profileString(basebasal, timeshift, percentage, true); - } - - String icString() { - return profileString(baseic, timeshift, percentage, false); - } - - String isfString() { - return profileString(baseisf, timeshift, percentage, false); - } - - String baseIcString() { - return profileString(baseic, 0, 100, false); - } - - String baseIsfString() { - return profileString(baseisf, 0, 100, false); - } - - String baseBasalString() { - return profileString(basebasal, 0, 100, true); - } - - public double baseBasalSum() { - return sum(basebasal); - } - - public double percentageBasalSum() { - double result = 0; - for (int i = 0; i < basebasal.length; i++) { - result += SafeParse.stringToDouble(DecimalFormatter.to2Decimal(basebasal[i] * percentage / 100d)); - } - return result; - } - - - public static double sum(double values[]) { - double result = 0; - for (int i = 0; i < values.length; i++) { - result += values[i]; - } - return result; - } - - - private static String profileString(double[] values, int timeshift, int percentage, boolean inc) { - timeshift = -(timeshift % 24) + 24; - StringBuilder sb = new StringBuilder(); - sb.append(""); - sb.append(0); - sb.append("h: "); - sb.append(""); - sb.append(DecimalFormatter.to2Decimal(values[(timeshift + 0) % 24] * (inc ? percentage / 100d : 100d / percentage))); - double prevVal = values[(timeshift + 0) % 24]; - for (int i = 1; i < 24; i++) { - if (prevVal != values[(timeshift + i) % 24]) { - sb.append(", "); - sb.append(""); - sb.append(i); - sb.append("h: "); - sb.append(""); - sb.append(DecimalFormatter.to2Decimal(values[(timeshift + i) % 24] * (inc ? percentage / 100d : 100d / percentage))); - prevVal = values[(timeshift + i) % 24]; - } - } - return sb.toString(); - } - - public int getPercentage() { - return percentage; - } - - public int getTimeshift() { - return timeshift; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileLocal/LocalProfileFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileLocal/LocalProfileFragment.java index 364484aff8..a325f08ad2 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileLocal/LocalProfileFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileLocal/LocalProfileFragment.java @@ -4,6 +4,7 @@ package info.nightscout.androidaps.plugins.ProfileLocal; import android.app.Activity; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.v4.app.FragmentTransaction; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; @@ -13,7 +14,6 @@ import android.widget.Button; import android.widget.RadioButton; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; import com.squareup.otto.Subscribe; import org.slf4j.Logger; @@ -33,6 +33,7 @@ import info.nightscout.androidaps.plugins.Careportal.OptionsToShow; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.NumberPicker; import info.nightscout.utils.SafeParse; import info.nightscout.utils.TimeListEdit; @@ -48,45 +49,43 @@ public class LocalProfileFragment extends SubscriberFragment { TimeListEdit basalView; TimeListEdit targetView; Button profileswitchButton; + Button resetButton; + Button saveButton; + TextView invalidProfile; + Runnable save = () -> { + doEdit(); + if (basalView != null) { + basalView.updateLabel(MainApp.sResources.getString(R.string.nsprofileview_basal_label) + ": " + getSumLabel()); + } + }; + + TextWatcher textWatch = new TextWatcher() { + + @Override + public void afterTextChanged(Editable s) { + } + + @Override + public void beforeTextChanged(CharSequence s, int start, + int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, + int before, int count) { + LocalProfilePlugin.getPlugin().dia = SafeParse.stringToDouble(diaView.getText().toString()); + doEdit(); + } + }; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { try { - Runnable save = new Runnable() { - @Override - public void run() { - LocalProfilePlugin.getPlugin().storeSettings(); - if (basalView != null) { - basalView.updateLabel(MainApp.sResources.getString(R.string.nsprofileview_basal_label) + ": " + getSumLabel()); - } - updateGUI(); - } - }; - - TextWatcher textWatch = new TextWatcher() { - - @Override - public void afterTextChanged(Editable s) { - } - - @Override - public void beforeTextChanged(CharSequence s, int start, - int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, - int before, int count) { - LocalProfilePlugin.getPlugin().dia = SafeParse.stringToDouble(diaView.getText().toString()); - LocalProfilePlugin.getPlugin().storeSettings(); - updateGUI(); - } - }; PumpDescription pumpDescription = ConfigBuilderPlugin.getActivePump().getPumpDescription(); - View layout = inflater.inflate(R.layout.localprofile_fragment, container, false); diaView = (NumberPicker) layout.findViewById(R.id.localprofile_dia); diaView.setParams(LocalProfilePlugin.getPlugin().dia, 2d, 48d, 0.1d, new DecimalFormat("0.0"), false, textWatch); @@ -97,6 +96,10 @@ public class LocalProfileFragment extends SubscriberFragment { basalView = new TimeListEdit(getContext(), layout, R.id.localprofile_basal, MainApp.sResources.getString(R.string.nsprofileview_basal_label) + ": " + getSumLabel(), LocalProfilePlugin.getPlugin().basal, null, pumpDescription.basalMinimumRate, 10, 0.01d, new DecimalFormat("0.00"), save); targetView = new TimeListEdit(getContext(), layout, R.id.localprofile_target, MainApp.sResources.getString(R.string.nsprofileview_target_label) + ":", LocalProfilePlugin.getPlugin().targetLow, LocalProfilePlugin.getPlugin().targetHigh, 3d, 200, 0.1d, new DecimalFormat("0.0"), save); profileswitchButton = (Button) layout.findViewById(R.id.localprofile_profileswitch); + resetButton = (Button) layout.findViewById(R.id.localprofile_reset); + saveButton = (Button) layout.findViewById(R.id.localprofile_save); + + invalidProfile = (TextView) layout.findViewById(R.id.invalidprofile); if (!ConfigBuilderPlugin.getActivePump().getPumpDescription().isTempBasalCapable) { @@ -112,7 +115,7 @@ public class LocalProfileFragment extends SubscriberFragment { LocalProfilePlugin.getPlugin().mgdl = mgdlView.isChecked(); LocalProfilePlugin.getPlugin().mmol = !LocalProfilePlugin.getPlugin().mgdl; mmolView.setChecked(LocalProfilePlugin.getPlugin().mmol); - LocalProfilePlugin.getPlugin().storeSettings(); + doEdit(); } }); mmolView.setOnClickListener(new View.OnClickListener() { @@ -121,7 +124,7 @@ public class LocalProfileFragment extends SubscriberFragment { LocalProfilePlugin.getPlugin().mmol = mmolView.isChecked(); LocalProfilePlugin.getPlugin().mgdl = !LocalProfilePlugin.getPlugin().mmol; mgdlView.setChecked(LocalProfilePlugin.getPlugin().mgdl); - LocalProfilePlugin.getPlugin().storeSettings(); + doEdit(); } }); @@ -136,19 +139,43 @@ public class LocalProfileFragment extends SubscriberFragment { } }); + resetButton.setOnClickListener(view -> { + LocalProfilePlugin.getPlugin().loadSettings(); + mgdlView.setChecked(LocalProfilePlugin.getPlugin().mgdl); + mmolView.setChecked(LocalProfilePlugin.getPlugin().mmol); + diaView.setParams(LocalProfilePlugin.getPlugin().dia, 2d, 48d, 0.1d, new DecimalFormat("0.0"), false, textWatch); + icView = new TimeListEdit(getContext(), layout, R.id.localprofile_ic, MainApp.sResources.getString(R.string.nsprofileview_ic_label) + ":", LocalProfilePlugin.getPlugin().ic, null, 0.5, 50d, 0.1d, new DecimalFormat("0.0"), save); + isfView = new TimeListEdit(getContext(), layout, R.id.localprofile_isf, MainApp.sResources.getString(R.string.nsprofileview_isf_label) + ":", LocalProfilePlugin.getPlugin().isf, null, 0.5, 500d, 0.1d, new DecimalFormat("0.0"), save); + basalView = new TimeListEdit(getContext(), layout, R.id.localprofile_basal, MainApp.sResources.getString(R.string.nsprofileview_basal_label) + ": " + getSumLabel(), LocalProfilePlugin.getPlugin().basal, null, pumpDescription.basalMinimumRate, 10, 0.01d, new DecimalFormat("0.00"), save); + targetView = new TimeListEdit(getContext(), layout, R.id.localprofile_target, MainApp.sResources.getString(R.string.nsprofileview_target_label) + ":", LocalProfilePlugin.getPlugin().targetLow, LocalProfilePlugin.getPlugin().targetHigh, 3d, 200, 0.1d, new DecimalFormat("0.0"), save); + updateGUI(); + }); + + saveButton.setOnClickListener(view -> { + if(!LocalProfilePlugin.getPlugin().isValidEditState()){ + return; //Should not happen as saveButton should not be visible if not valid + } + LocalProfilePlugin.getPlugin().storeSettings(); + updateGUI(); + }); return layout; } catch (Exception e) { log.error("Unhandled exception: ", e); - Crashlytics.logException(e); + FabricPrivacy.logException(e); } return null; } + public void doEdit() { + LocalProfilePlugin.getPlugin().setEdited(true); + updateGUI(); + } + @NonNull public String getSumLabel() { - ProfileStore profile = LocalProfilePlugin.getPlugin().getProfile(); + ProfileStore profile = LocalProfilePlugin.getPlugin().createProfileStore(); if (profile != null) return " ∑" + DecimalFormatter.to2Decimal(profile.getDefaultProfile().baseBasalSum()) + "U"; else @@ -167,16 +194,39 @@ public class LocalProfileFragment extends SubscriberFragment { activity.runOnUiThread(new Runnable() { @Override public void run() { - boolean isValid = LocalProfilePlugin.getPlugin().getProfile() != null && LocalProfilePlugin.getPlugin().getProfile().getDefaultProfile().isValid(MainApp.gs(R.string.localprofile)); - if (!ConfigBuilderPlugin.getActivePump().isInitialized() || ConfigBuilderPlugin.getActivePump().isSuspended() || !isValid) { - profileswitchButton.setVisibility(View.GONE); + boolean isValid = LocalProfilePlugin.getPlugin().isValidEditState(); + boolean isEdited = LocalProfilePlugin.getPlugin().isEdited(); + if (isValid) { + invalidProfile.setVisibility(View.GONE); //show invalid profile + + if (isEdited || !ConfigBuilderPlugin.getActivePump().isInitialized() || ConfigBuilderPlugin.getActivePump().isSuspended()) { + //edited profile -> save first + //pump not initialized -> don't update profile yet + profileswitchButton.setVisibility(View.GONE); + } else { + profileswitchButton.setVisibility(View.VISIBLE); + } + + if(isEdited){ + saveButton.setVisibility(View.VISIBLE); + } else { + saveButton.setVisibility(View.GONE); + + } + + } else { - profileswitchButton.setVisibility(View.VISIBLE); - } - if (isValid) - invalidProfile.setVisibility(View.GONE); - else invalidProfile.setVisibility(View.VISIBLE); + profileswitchButton.setVisibility(View.GONE); + saveButton.setVisibility(View.GONE); //don't save an invalid profile + } + + //Show reset button iff data was edited + if(isEdited) { + resetButton.setVisibility(View.VISIBLE); + } else { + resetButton.setVisibility(View.GONE); + } } }); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileLocal/LocalProfilePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileLocal/LocalProfilePlugin.java index 69272cc2ae..ce190b691b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileLocal/LocalProfilePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileLocal/LocalProfilePlugin.java @@ -1,7 +1,6 @@ package info.nightscout.androidaps.plugins.ProfileLocal; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; +import android.support.annotation.NonNull; import org.json.JSONArray; import org.json.JSONException; @@ -15,6 +14,8 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.ProfileInterface; import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.SP; @@ -22,7 +23,7 @@ import info.nightscout.utils.SP; /** * Created by mike on 05.08.2016. */ -public class LocalProfilePlugin implements PluginBase, ProfileInterface { +public class LocalProfilePlugin extends PluginBase implements ProfileInterface { public static final String LOCAL_PROFILE = "LocalProfile"; private static Logger log = LoggerFactory.getLogger(LocalProfilePlugin.class); @@ -34,14 +35,19 @@ public class LocalProfilePlugin implements PluginBase, ProfileInterface { return localProfilePlugin; } - private boolean fragmentEnabled = false; - private boolean fragmentVisible = false; - private ProfileStore convertedProfile = null; - private String convertedProfileName = null; - public static final String DEFAULTARRAY = "[{\"time\":\"00:00\",\"timeAsSeconds\":0,\"value\":0}]"; + private static final String DEFAULTARRAY = "[{\"time\":\"00:00\",\"timeAsSeconds\":0,\"value\":0}]"; + public boolean isEdited() { + return edited; + } + + public void setEdited(boolean edited) { + this.edited = edited; + } + + boolean edited; boolean mgdl; boolean mmol; Double dia; @@ -52,94 +58,32 @@ public class LocalProfilePlugin implements PluginBase, ProfileInterface { JSONArray targetHigh; public LocalProfilePlugin() { + super(new PluginDescription() + .mainType(PluginType.PROFILE) + .fragmentClass(LocalProfileFragment.class.getName()) + .pluginName(R.string.localprofile) + .shortName(R.string.localprofile_shortname) + ); loadSettings(); } - @Override - public String getFragmentClass() { - return LocalProfileFragment.class.getName(); - } + public synchronized void storeSettings() { + SP.putBoolean(LOCAL_PROFILE + "mmol", mmol); + SP.putBoolean(LOCAL_PROFILE + "mgdl", mgdl); + SP.putString(LOCAL_PROFILE + "dia", dia.toString()); + SP.putString(LOCAL_PROFILE + "ic", ic.toString()); + SP.putString(LOCAL_PROFILE + "isf", isf.toString()); + SP.putString(LOCAL_PROFILE + "basal", basal.toString()); + SP.putString(LOCAL_PROFILE + "targetlow", targetLow.toString()); + SP.putString(LOCAL_PROFILE + "targethigh", targetHigh.toString()); - @Override - public int getType() { - return PluginBase.PROFILE; - } - - @Override - public String getName() { - return MainApp.instance().getString(R.string.localprofile); - } - - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.localprofile_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == PROFILE && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == PROFILE && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == PROFILE) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == PROFILE) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return -1; - } - - public void storeSettings() { - SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(MainApp.instance().getApplicationContext()); - SharedPreferences.Editor editor = settings.edit(); - editor.putBoolean(LOCAL_PROFILE + "mmol", mmol); - editor.putBoolean(LOCAL_PROFILE + "mgdl", mgdl); - editor.putString(LOCAL_PROFILE + "dia", dia.toString()); - editor.putString(LOCAL_PROFILE + "ic", ic.toString()); - editor.putString(LOCAL_PROFILE + "isf", isf.toString()); - editor.putString(LOCAL_PROFILE + "basal", basal.toString()); - editor.putString(LOCAL_PROFILE + "targetlow", targetLow.toString()); - editor.putString(LOCAL_PROFILE + "targethigh", targetHigh.toString()); - - editor.apply(); - createConvertedProfile(); + createAndStoreConvertedProfile(); + edited = false; if (Config.logPrefsChange) log.debug("Storing settings: " + getRawProfile().getData().toString()); } - public void loadSettings() { + public synchronized void loadSettings() { if (Config.logPrefsChange) log.debug("Loading stored settings"); @@ -186,6 +130,8 @@ public class LocalProfilePlugin implements PluginBase, ProfileInterface { } catch (JSONException ignored) { } } + edited = false; + createAndStoreConvertedProfile(); } /* @@ -226,7 +172,16 @@ public class LocalProfilePlugin implements PluginBase, ProfileInterface { "created_at": "2016-06-16T08:34:41.256Z" } */ - private void createConvertedProfile() { + private void createAndStoreConvertedProfile() { + convertedProfile = createProfileStore(); + } + + public synchronized boolean isValidEditState() { + return createProfileStore().getDefaultProfile().isValid(MainApp.gs(R.string.localprofile)); + } + + @NonNull + public ProfileStore createProfileStore() { JSONObject json = new JSONObject(); JSONObject store = new JSONObject(); JSONObject profile = new JSONObject(); @@ -245,22 +200,17 @@ public class LocalProfilePlugin implements PluginBase, ProfileInterface { } catch (JSONException e) { log.error("Unhandled exception", e); } - convertedProfile = new ProfileStore(json); - convertedProfileName = LOCAL_PROFILE; + return new ProfileStore(json); } @Override public ProfileStore getProfile() { - if (convertedProfile == null) - createConvertedProfile(); if (!convertedProfile.getDefaultProfile().isValid(MainApp.gs(R.string.localprofile))) return null; return convertedProfile; } public ProfileStore getRawProfile() { - if (convertedProfile == null) - createConvertedProfile(); return convertedProfile; } @@ -271,8 +221,6 @@ public class LocalProfilePlugin implements PluginBase, ProfileInterface { @Override public String getProfileName() { - if (convertedProfile == null) - createConvertedProfile(); return DecimalFormatter.to2Decimal(convertedProfile.getDefaultProfile().percentageBasalSum()) + "U "; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileNS/NSProfileFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileNS/NSProfileFragment.java index c7d5d8edbf..dca9b183a3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileNS/NSProfileFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileNS/NSProfileFragment.java @@ -5,58 +5,71 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.Button; import android.widget.Spinner; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; import com.squareup.otto.Subscribe; import java.util.ArrayList; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.OnItemSelected; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.ProfileStore; +import info.nightscout.androidaps.plugins.Careportal.Dialogs.NewNSTreatmentDialog; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; import info.nightscout.androidaps.plugins.ProfileNS.events.EventNSProfileUpdateGUI; +import info.nightscout.androidaps.plugins.Treatments.fragments.ProfileGraph; import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.FabricPrivacy; +import info.nightscout.utils.OKDialog; + +import static butterknife.OnItemSelected.Callback.NOTHING_SELECTED; -public class NSProfileFragment extends SubscriberFragment implements AdapterView.OnItemSelectedListener { - private Spinner profileSpinner; - private TextView noProfile; - private TextView units; - private TextView dia; - private TextView activeProfile; - private TextView ic; - private TextView isf; - private TextView basal; - private TextView target; +public class NSProfileFragment extends SubscriberFragment { + @BindView(R.id.nsprofile_spinner) + Spinner profileSpinner; + @BindView(R.id.profileview_noprofile) + TextView noProfile; + @BindView(R.id.profileview_invalidprofile) + TextView invalidProfile; + @BindView(R.id.profileview_units) + TextView units; + @BindView(R.id.profileview_dia) + TextView dia; + @BindView(R.id.profileview_activeprofile) + TextView activeProfile; + @BindView(R.id.profileview_ic) + TextView ic; + @BindView(R.id.profileview_isf) + TextView isf; + @BindView(R.id.profileview_basal) + TextView basal; + @BindView(R.id.profileview_target) + TextView target; + @BindView(R.id.basal_graph) + ProfileGraph basalGraph; + @BindView(R.id.nsprofile_profileswitch) + Button activateButton; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { try { - View layout = inflater.inflate(R.layout.nsprofile_fragment, container, false); - - profileSpinner = (Spinner) layout.findViewById(R.id.nsprofile_spinner); - noProfile = (TextView) layout.findViewById(R.id.profileview_noprofile); - units = (TextView) layout.findViewById(R.id.profileview_units); - dia = (TextView) layout.findViewById(R.id.profileview_dia); - activeProfile = (TextView) layout.findViewById(R.id.profileview_activeprofile); - ic = (TextView) layout.findViewById(R.id.profileview_ic); - isf = (TextView) layout.findViewById(R.id.profileview_isf); - basal = (TextView) layout.findViewById(R.id.profileview_basal); - target = (TextView) layout.findViewById(R.id.profileview_target); - - profileSpinner.setOnItemSelectedListener(this); + View view = inflater.inflate(R.layout.nsprofile_fragment, container, false); + unbinder = ButterKnife.bind(this, view); updateGUI(); - return layout; + return view; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } return null; @@ -66,23 +79,11 @@ public class NSProfileFragment extends SubscriberFragment implements AdapterView public void onStatusEvent(final EventNSProfileUpdateGUI ev) { Activity activity = getActivity(); if (activity != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - updateGUI(); - } - }); + activity.runOnUiThread(() -> updateGUI()); } @Override protected void updateGUI() { - if (MainApp.getConfigBuilder().getProfile() == null) { - noProfile.setVisibility(View.VISIBLE); - return; - } else { - noProfile.setVisibility(View.GONE); - } - ProfileStore profileStore = NSProfilePlugin.getPlugin().getProfile(); if (profileStore != null) { ArrayList profileList = profileStore.getProfileList(); @@ -94,12 +95,15 @@ public class NSProfileFragment extends SubscriberFragment implements AdapterView if (profileList.get(p).equals(MainApp.getConfigBuilder().getProfileName())) profileSpinner.setSelection(p); } + noProfile.setVisibility(View.GONE); + } else { + noProfile.setVisibility(View.VISIBLE); } } - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - String name = parent.getItemAtPosition(position).toString(); + @OnItemSelected(R.id.nsprofile_spinner) + public void onItemSelected(Spinner spinner, int position) { + String name = spinner.getItemAtPosition(position).toString(); ProfileStore store = NSProfilePlugin.getPlugin().getProfile(); if (store != null) { @@ -112,12 +116,23 @@ public class NSProfileFragment extends SubscriberFragment implements AdapterView isf.setText(profile.getIsfList()); basal.setText(profile.getBasalList()); target.setText(profile.getTargetList()); + basalGraph.show(profile); } + if (profile.isValid("NSProfileFragment")) { + invalidProfile.setVisibility(View.GONE); + activateButton.setVisibility(View.VISIBLE); + } else { + invalidProfile.setVisibility(View.VISIBLE); + activateButton.setVisibility(View.GONE); + } + } else { + activateButton.setVisibility(View.GONE); } } - @Override - public void onNothingSelected(AdapterView parent) { + @OnItemSelected(value = R.id.nsprofile_spinner, callback = NOTHING_SELECTED) + public void onNothingSelected() { + invalidProfile.setVisibility(View.VISIBLE); noProfile.setVisibility(View.VISIBLE); units.setText(""); dia.setText(""); @@ -126,5 +141,20 @@ public class NSProfileFragment extends SubscriberFragment implements AdapterView isf.setText(""); basal.setText(""); target.setText(""); + activateButton.setVisibility(View.GONE); + } + + @OnClick(R.id.nsprofile_profileswitch) + public void onClickProfileSwitch() { + String name = profileSpinner.getSelectedItem() != null ? profileSpinner.getSelectedItem().toString() : ""; + ProfileStore store = NSProfilePlugin.getPlugin().getProfile(); + if (store != null) { + Profile profile = store.getSpecificProfile(name); + if (profile != null) { + OKDialog.showConfirmation(getActivity(), MainApp.gs(R.string.activate_profile) + ": " + name + " ?", () -> + NewNSTreatmentDialog.doProfileSwitch(store, name, 0, 100, 0) + ); + } + } } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileNS/NSProfilePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileNS/NSProfilePlugin.java index d3fedfd9c8..dfd62cda70 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileNS/NSProfilePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileNS/NSProfilePlugin.java @@ -17,17 +17,16 @@ import info.nightscout.androidaps.R; import info.nightscout.androidaps.Services.Intents; import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.ProfileInterface; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.ProfileNS.events.EventNSProfileUpdateGUI; -import info.nightscout.androidaps.plugins.SmsCommunicator.SmsCommunicatorPlugin; -import info.nightscout.androidaps.queue.Callback; import info.nightscout.utils.SP; /** * Created by mike on 05.08.2016. */ -public class NSProfilePlugin implements PluginBase, ProfileInterface { +public class NSProfilePlugin extends PluginBase implements ProfileInterface { private static Logger log = LoggerFactory.getLogger(NSProfilePlugin.class); private static NSProfilePlugin nsProfilePlugin; @@ -38,102 +37,40 @@ public class NSProfilePlugin implements PluginBase, ProfileInterface { return nsProfilePlugin; } - @Override - public String getFragmentClass() { - return NSProfileFragment.class.getName(); - } - - private boolean fragmentEnabled = true; - private boolean fragmentVisible = true; - - private static ProfileStore profile = null; + private ProfileStore profile = null; private NSProfilePlugin() { - MainApp.bus().register(this); + super(new PluginDescription() + .mainType(PluginType.PROFILE) + .fragmentClass(NSProfileFragment.class.getName()) + .pluginName(R.string.profileviewer) + .shortName(R.string.profileviewer_shortname) + .alwaysEnabled(Config.NSCLIENT) + .alwayVisible(Config.NSCLIENT) + .showInList(!Config.NSCLIENT) + ); loadNSProfile(); - } @Override - public String getName() { - return MainApp.instance().getString(R.string.profileviewer); + protected void onStart() { + MainApp.bus().register(this); + super.onStart(); } @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.profileviewer_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == PROFILE && (Config.NSCLIENT || Config.G5UPLOADER|| fragmentEnabled); - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == PROFILE && (Config.NSCLIENT || Config.G5UPLOADER|| fragmentVisible); - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return !Config.NSCLIENT && !Config.G5UPLOADER; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == PROFILE) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == PROFILE) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return -1; - } - - @Override - public int getType() { - return PluginBase.PROFILE; + protected void onStop() { + MainApp.bus().unregister(this); } @Subscribe - public static void storeNewProfile(ProfileStore newProfile) { + public void storeNewProfile(ProfileStore newProfile) { profile = new ProfileStore(newProfile.getData()); storeNSProfile(); MainApp.bus().post(new EventNSProfileUpdateGUI()); - ConfigBuilderPlugin.getCommandQueue().setProfile(MainApp.getConfigBuilder().getProfile(), new Callback() { - @Override - public void run() { - if (result.enacted) { - SmsCommunicatorPlugin smsCommunicatorPlugin = MainApp.getSpecificPlugin(SmsCommunicatorPlugin.class); - if (smsCommunicatorPlugin != null && smsCommunicatorPlugin.isEnabled(PluginBase.GENERAL)) { - smsCommunicatorPlugin.sendNotificationToAllNumbers(MainApp.sResources.getString(R.string.profile_set_ok)); - } - } - } - }); } - private static void storeNSProfile() { + private void storeNSProfile() { SP.putString("profile", profile.getData().toString()); if (Config.logPrefsChange) log.debug("Storing profile"); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileSimple/SimpleProfileFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileSimple/SimpleProfileFragment.java index 9a0b64f6d1..b3b2226705 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileSimple/SimpleProfileFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileSimple/SimpleProfileFragment.java @@ -13,7 +13,6 @@ import android.widget.EditText; import android.widget.RadioButton; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; import com.squareup.otto.Subscribe; import org.slf4j.Logger; @@ -27,6 +26,7 @@ import info.nightscout.androidaps.plugins.Careportal.Dialogs.NewNSTreatmentDialo import info.nightscout.androidaps.plugins.Careportal.OptionsToShow; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.SafeParse; public class SimpleProfileFragment extends SubscriberFragment { @@ -137,7 +137,7 @@ public class SimpleProfileFragment extends SubscriberFragment { return layout; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } return null; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileSimple/SimpleProfilePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileSimple/SimpleProfilePlugin.java index b538a28512..adff378819 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/ProfileSimple/SimpleProfilePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/ProfileSimple/SimpleProfilePlugin.java @@ -15,13 +15,15 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.ProfileInterface; import info.nightscout.utils.SP; /** * Created by mike on 05.08.2016. */ -public class SimpleProfilePlugin implements PluginBase, ProfileInterface { +public class SimpleProfilePlugin extends PluginBase implements ProfileInterface { private static Logger log = LoggerFactory.getLogger(SimpleProfilePlugin.class); private static SimpleProfilePlugin simpleProfilePlugin; @@ -32,9 +34,6 @@ public class SimpleProfilePlugin implements PluginBase, ProfileInterface { return simpleProfilePlugin; } - private boolean fragmentEnabled = false; - private boolean fragmentVisible = false; - private static ProfileStore convertedProfile = null; boolean mgdl; @@ -47,75 +46,15 @@ public class SimpleProfilePlugin implements PluginBase, ProfileInterface { Double targetHigh; private SimpleProfilePlugin() { + super(new PluginDescription() + .mainType(PluginType.PROFILE) + .fragmentClass(SimpleProfileFragment.class.getName()) + .pluginName(R.string.simpleprofile) + .shortName(R.string.simpleprofile_shortname) + ); loadSettings(); } - @Override - public String getFragmentClass() { - return SimpleProfileFragment.class.getName(); - } - - @Override - public int getType() { - return PluginBase.PROFILE; - } - - @Override - public String getName() { - return MainApp.instance().getString(R.string.simpleprofile); - } - - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.simpleprofile_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == PROFILE && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == PROFILE && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == PROFILE) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == PROFILE) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return -1; - } - public void storeSettings() { if (Config.logPrefsChange) log.debug("Storing settings"); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboFragment.java new file mode 100644 index 0000000000..14fdd3c219 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboFragment.java @@ -0,0 +1,224 @@ +package info.nightscout.androidaps.plugins.PumpCombo; + + +import android.app.Activity; +import android.app.AlertDialog; +import android.graphics.Color; +import android.graphics.Typeface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import com.squareup.otto.Subscribe; + +import org.apache.commons.lang3.StringUtils; + +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpState; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Bolus; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.Common.SubscriberFragment; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.PumpCombo.events.EventComboPumpUpdateGUI; +import info.nightscout.androidaps.queue.Callback; +import info.nightscout.androidaps.queue.events.EventQueueChanged; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.SP; + +public class ComboFragment extends SubscriberFragment implements View.OnClickListener { + private TextView stateView; + private TextView activityView; + private TextView batteryView; + private TextView reservoirView; + private TextView lastConnectionView; + private TextView lastBolusView; + private TextView baseBasalRate; + private TextView tempBasalText; + private Button refreshButton; + private TextView bolusCount; + private TextView tbrCount; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.combopump_fragment, container, false); + + stateView = view.findViewById(R.id.combo_state); + activityView = view.findViewById(R.id.combo_activity); + batteryView = view.findViewById(R.id.combo_pumpstate_battery); + reservoirView = view.findViewById(R.id.combo_insulinstate); + lastBolusView = view.findViewById(R.id.combo_last_bolus); + lastConnectionView = view.findViewById(R.id.combo_lastconnection); + baseBasalRate = view.findViewById(R.id.combo_base_basal_rate); + tempBasalText = view.findViewById(R.id.combo_temp_basal); + bolusCount = view.findViewById(R.id.combo_bolus_count); + tbrCount = view.findViewById(R.id.combo_tbr_count); + + refreshButton = view.findViewById(R.id.combo_refresh_button); + refreshButton.setOnClickListener(this); + + updateGUI(); + return view; + } + + private void runOnUiThread(Runnable action) { + Activity activity = getActivity(); + if (activity != null) { + activity.runOnUiThread(action); + } + } + + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.combo_refresh_button: + refreshButton.setEnabled(false); + ConfigBuilderPlugin.getCommandQueue().readStatus("User request", new Callback() { + @Override + public void run() { + runOnUiThread(() -> refreshButton.setEnabled(true)); + } + }); + break; + } + } + + @Subscribe + public void onStatusEvent(final EventComboPumpUpdateGUI ignored) { + updateGUI(); + } + + @Subscribe + public void onStatusEvent(final EventQueueChanged ignored) { + updateGUI(); + } + + + public void updateGUI() { + runOnUiThread(() -> { + ComboPlugin plugin = ComboPlugin.getPlugin(); + + // state + stateView.setText(plugin.getStateSummary()); + PumpState ps = plugin.getPump().state; + if (ps.insulinState == PumpState.EMPTY || ps.batteryState == PumpState.EMPTY + || ps.activeAlert != null && ps.activeAlert.errorCode != null) { + stateView.setTextColor(Color.RED); + stateView.setTypeface(null, Typeface.BOLD); + } else if (plugin.getPump().state.suspended + || ps.activeAlert != null && ps.activeAlert.warningCode != null) { + stateView.setTextColor(Color.YELLOW); + stateView.setTypeface(null, Typeface.BOLD); + } else { + stateView.setTextColor(Color.WHITE); + stateView.setTypeface(null, Typeface.NORMAL); + } + + // activity + String activity = plugin.getPump().activity; + if (StringUtils.isNotEmpty(activity)) { + activityView.setTextSize(14); + activityView.setText(activity); + } else { + activityView.setTextSize(20); + activityView.setText("{fa-bed}"); + } + + if (plugin.isInitialized()) { + // battery + batteryView.setTextSize(20); + if (ps.batteryState == PumpState.EMPTY) { + batteryView.setText("{fa-battery-empty}"); + batteryView.setTextColor(Color.RED); + } else if (ps.batteryState == PumpState.LOW) { + batteryView.setText("{fa-battery-quarter}"); + batteryView.setTextColor(Color.YELLOW); + } else { + batteryView.setText("{fa-battery-full}"); + batteryView.setTextColor(Color.WHITE); + } + + // reservoir + int reservoirLevel = plugin.getPump().reservoirLevel; + if (reservoirLevel != -1) { + reservoirView.setText(reservoirLevel + " " + MainApp.sResources.getString(R.string.insulin_unit_shortname)); + } else if (ps.insulinState == PumpState.LOW) { + reservoirView.setText(MainApp.gs(R.string.combo_reservoir_low)); + } else if (ps.insulinState == PumpState.EMPTY) { + reservoirView.setText(MainApp.gs(R.string.combo_reservoir_empty)); + } else { + reservoirView.setText(MainApp.gs(R.string.combo_reservoir_normal)); + } + + if (ps.insulinState == PumpState.UNKNOWN) { + reservoirView.setTextColor(Color.WHITE); + reservoirView.setTypeface(null, Typeface.NORMAL); + } else if (ps.insulinState == PumpState.LOW) { + reservoirView.setTextColor(Color.YELLOW); + reservoirView.setTypeface(null, Typeface.BOLD); + } else if (ps.insulinState == PumpState.EMPTY) { + reservoirView.setTextColor(Color.RED); + reservoirView.setTypeface(null, Typeface.BOLD); + } else { + reservoirView.setTextColor(Color.WHITE); + reservoirView.setTypeface(null, Typeface.NORMAL); + } + + // last connection + String minAgo = DateUtil.minAgo(plugin.getPump().lastSuccessfulCmdTime); + long min = (System.currentTimeMillis() - plugin.getPump().lastSuccessfulCmdTime) / 1000 / 60; + if (plugin.getPump().lastSuccessfulCmdTime + 60 * 1000 > System.currentTimeMillis()) { + lastConnectionView.setText(R.string.combo_pump_connected_now); + lastConnectionView.setTextColor(Color.WHITE); + } else if (plugin.getPump().lastSuccessfulCmdTime + 30 * 60 * 1000 < System.currentTimeMillis()) { + lastConnectionView.setText(MainApp.gs(R.string.combo_no_pump_connection, min)); + lastConnectionView.setTextColor(Color.RED); + } else { + lastConnectionView.setText(minAgo); + lastConnectionView.setTextColor(Color.WHITE); + } + + // last bolus + Bolus bolus = plugin.getPump().lastBolus; + if (bolus != null) { + long agoMsc = System.currentTimeMillis() - bolus.timestamp; + double bolusMinAgo = agoMsc / 60d / 1000d; + String unit = MainApp.gs(R.string.insulin_unit_shortname); + String ago; + if ((agoMsc < 60 * 1000)) { + ago = MainApp.gs(R.string.combo_pump_connected_now); + } else if (bolusMinAgo < 60) { + ago = DateUtil.minAgo(bolus.timestamp); + } else { + ago = DateUtil.hourAgo(bolus.timestamp); + } + lastBolusView.setText(MainApp.gs(R.string.combo_last_bolus, bolus.amount, unit, ago)); + } else { + lastBolusView.setText(""); + } + + // base basal rate + baseBasalRate.setText(MainApp.gs(R.string.pump_basebasalrate, plugin.getBaseBasalRate())); + + // TBR + String tbrStr = ""; + if (ps.tbrPercent != -1 && ps.tbrPercent != 100) { + long minSinceRead = (System.currentTimeMillis() - plugin.getPump().state.timestamp) / 1000 / 60; + long remaining = ps.tbrRemainingDuration - minSinceRead; + if (remaining >= 0) { + tbrStr = MainApp.gs(R.string.combo_tbr_remaining, ps.tbrPercent, remaining); + } + } + tempBasalText.setText(tbrStr); + + // stats + bolusCount.setText(String.valueOf(SP.getLong(ComboPlugin.COMBO_BOLUSES_DELIVERED, 0L))); + tbrCount.setText(String.valueOf(SP.getLong(ComboPlugin.COMBO_TBRS_SET, 0L))); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPlugin.java new file mode 100644 index 0000000000..68db1658f6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPlugin.java @@ -0,0 +1,1371 @@ +package info.nightscout.androidaps.plugins.PumpCombo; + +import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.crashlytics.android.answers.CustomEvent; + +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +import info.nightscout.androidaps.BuildConfig; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.db.CareportalEvent; +import info.nightscout.androidaps.db.Source; +import info.nightscout.androidaps.db.TDD; +import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.plugins.Treatments.Treatment; +import info.nightscout.androidaps.events.EventInitializationChanged; +import info.nightscout.androidaps.events.EventRefreshOverview; +import info.nightscout.androidaps.interfaces.Constraint; +import info.nightscout.androidaps.interfaces.ConstraintsInterface; +import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.interfaces.PumpDescription; +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; +import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; +import info.nightscout.androidaps.plugins.PumpCombo.events.EventComboPumpUpdateGUI; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BasalProfile; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BolusProgressReporter; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.CommandResult; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpState; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpWarningCodes; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.RuffyCommands; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.RuffyScripter; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.WarningOrErrorCode; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Bolus; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpHistory; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpHistoryRequest; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Tdd; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; +import info.nightscout.androidaps.queue.Callback; +import info.nightscout.androidaps.queue.CommandQueue; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.FabricPrivacy; +import info.nightscout.utils.SP; + +/** + * Created by mike on 05.08.2016. + */ +public class ComboPlugin extends PluginBase implements PumpInterface, ConstraintsInterface { + private static final Logger log = LoggerFactory.getLogger(ComboPlugin.class); + static final String COMBO_TBRS_SET = "combo_tbrs_set"; + static final String COMBO_BOLUSES_DELIVERED = "combo_boluses_delivered"; + + private static ComboPlugin plugin = null; + + public static ComboPlugin getPlugin() { + if (plugin == null) + plugin = new ComboPlugin(); + return plugin; + } + + private final static PumpDescription pumpDescription = new PumpDescription(); + + static { + // these properties can't be changed on the pump, some via desktop configuration software + pumpDescription.isBolusCapable = true; + pumpDescription.bolusStep = 0.1d; + + pumpDescription.isExtendedBolusCapable = false; + pumpDescription.extendedBolusStep = 0.1d; + pumpDescription.extendedBolusDurationStep = 15; + pumpDescription.extendedBolusMaxDuration = 12 * 60; + + pumpDescription.isTempBasalCapable = true; + pumpDescription.tempBasalStyle = PumpDescription.PERCENT; + + pumpDescription.maxTempPercent = 500; + pumpDescription.tempPercentStep = 10; + + pumpDescription.tempDurationStep = 15; + pumpDescription.tempDurationStep15mAllowed = true; + pumpDescription.tempDurationStep30mAllowed = true; + pumpDescription.tempMaxDuration = 24 * 60; + + + pumpDescription.isSetBasalProfileCapable = true; + pumpDescription.basalStep = 0.01d; + pumpDescription.basalMinimumRate = 0.05d; + + pumpDescription.isRefillingCapable = true; + + pumpDescription.storesCarbInfo = false; + + pumpDescription.is30minBasalRatesCapable = false; + + pumpDescription.supportsTDDs = true; + pumpDescription.needsManualTDDLoad = true; + } + + @NonNull + private final RuffyCommands ruffyScripter; + + @NonNull + private static final ComboPump pump = new ComboPump(); + + /** + * This is used to determine when to pass a bolus cancel request to the scripter + */ + private volatile boolean scripterIsBolusing; + /** + * This is set to true to request a bolus cancellation. {@link #deliverBolus(DetailedBolusInfo)} + * will reset this flag. + */ + private volatile boolean cancelBolus; + + /** + * This is set (in {@link #checkHistory()} whenever a connection to the pump is made and + * indicates if new history records on the pump have been found. This effectively blocks + * high temps ({@link #setTempBasalPercent(Integer, Integer)} and boluses + * ({@link #deliverBolus(DetailedBolusInfo)} till the queue is empty and the connection + * is shut down. + * {@link #initializePump()} resets this since on startup the history is allowed to have + * changed (and the user can't possible have already calculated anything with out of date IOB). + * The next reconnect will then reset this flag. This might cause some grief when attempting + * to bolus again within the 5s of idling it takes before the connecting is shut down. Or if + * the queue is very large, giving the user more time to input boluses. I don't have a good + * solution for this at the moment, but this is enough of an edge case - faulting in the right + * direction - so that adding more complexity yields little benefit. + */ + private volatile boolean pumpHistoryChanged = false; + + /** + * Cache of the last <=2 boluses on the pump. Used to detect changes in pump history, + * requiring reading more pump history. This is read/set in {@link #checkHistory()} when changed + * pump history was detected and was read, as well as in {@link #deliverBolus(DetailedBolusInfo)} + * after bolus delivery. Newest record is the first one. + */ + private volatile List recentBoluses = new ArrayList<>(0); + + private static final PumpEnactResult OPERATION_NOT_SUPPORTED = new PumpEnactResult() + .success(false).enacted(false).comment(MainApp.gs(R.string.combo_pump_unsupported_operation)); + + private ComboPlugin() { + super(new PluginDescription() + .mainType(PluginType.PUMP) + .fragmentClass(ComboFragment.class.getName()) + .pluginName(R.string.combopump) + .shortName(R.string.combopump_shortname) + ); + ruffyScripter = new RuffyScripter(MainApp.instance().getApplicationContext()); + } + + public ComboPump getPump() { + return pump; + } + + String getStateSummary() { + PumpState ps = pump.state; + if (ps.activeAlert != null) { + return ps.activeAlert.errorCode != null + ? "E" + ps.activeAlert.errorCode + ": " + ps.activeAlert.message + : "W" + ps.activeAlert.warningCode + ": " + ps.activeAlert.message; + } else if (ps.suspended && (ps.batteryState == PumpState.EMPTY || ps.insulinState == PumpState.EMPTY)) { + return MainApp.gs(R.string.combo_pump_state_suspended_due_to_error); + } else if (ps.suspended) { + return MainApp.gs(R.string.combo_pump_state_suspended_by_user); + } else if (!pump.initialized) { + return MainApp.gs(R.string.combo_pump_state_initializing); + } else if (!validBasalRateProfileSelectedOnPump) { + return MainApp.gs(R.string.loopdisabled); + } + return MainApp.gs(R.string.combo_pump_state_running); + } + + @Override + public boolean isInitialized() { + return pump.initialized; + } + + @Override + public boolean isSuspended() { + return pump.state.suspended; + } + + @Override + public boolean isBusy() { + return ruffyScripter.isPumpBusy(); + } + + @Override + public boolean isConnected() { + return true; + } + + @Override + public boolean isConnecting() { + return false; + } + + @Override + public void connect(String reason) { + // ruffyscripter establishes a connection as needed. + // ComboPlugin.runCommand performs on connect checks if needed, thus needs info on + // whether a connection is there. + // More importantly, RuffyScripter needs control over connection to be able to recover + // from a failure and deal with alarms on pump. + // Yes, this could also be done by keeping a flag 'inCmd' in RuffyScripter, which kicks + // off recovery unless set to false again after command completion and have connect + // checks be called in ComboPlugin.connect(); ... and have that cause other issues + } + + @Override + public void disconnect(String reason) { + log.debug("Disconnect called with reason: " + reason); + ruffyScripter.disconnect(); + } + + @Override + public void stopConnecting() { + // we're not doing that + } + + @Override + public synchronized PumpEnactResult setNewBasalProfile(Profile profile) { + if (!isInitialized()) { + // note that this should not happen anymore since the queue is present, which + // issues a READSTATE when starting to issue commands which initializes the pump + log.error("setNewBasalProfile not initialized"); + Notification notification = new Notification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED, MainApp.gs(R.string.pumpNotInitializedProfileNotSet), Notification.URGENT); + MainApp.bus().post(new EventNewNotification(notification)); + return new PumpEnactResult().success(false).enacted(false).comment(MainApp.gs(R.string.pumpNotInitializedProfileNotSet)); + } + + BasalProfile requestedBasalProfile = convertProfileToComboProfile(profile); + if (pump.basalProfile.equals(requestedBasalProfile)) { + //dismiss previously "FAILED" overview notifications + MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); + MainApp.bus().post(new EventDismissNotification(Notification.FAILED_UDPATE_PROFILE)); + return new PumpEnactResult().success(true).enacted(false); + } + + CommandResult stateResult = runCommand(null, 1, ruffyScripter::readPumpState); + if (stateResult.state.unsafeUsageDetected == PumpState.UNSUPPORTED_BASAL_RATE_PROFILE) { + return new PumpEnactResult().success(false).enacted(false).comment(MainApp.gs(R.string.combo_force_disabled_notification)); + } + + CommandResult setResult = runCommand(MainApp.gs(R.string.combo_activity_setting_basal_profile), 2, + () -> ruffyScripter.setBasalProfile(requestedBasalProfile)); + if (!setResult.success) { + Notification notification = new Notification(Notification.FAILED_UDPATE_PROFILE, MainApp.gs(R.string.failedupdatebasalprofile), Notification.URGENT); + MainApp.bus().post(new EventNewNotification(notification)); + return new PumpEnactResult().success(false).enacted(false).comment(MainApp.gs(R.string.failedupdatebasalprofile)); + } + + pump.basalProfile = requestedBasalProfile; + + //dismiss previously "FAILED" overview notifications + MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); + MainApp.bus().post(new EventDismissNotification(Notification.FAILED_UDPATE_PROFILE)); + //issue success notification + Notification notification = new Notification(Notification.PROFILE_SET_OK, MainApp.gs(R.string.profile_set_ok), Notification.INFO, 60); + MainApp.bus().post(new EventNewNotification(notification)); + return new PumpEnactResult().success(true).enacted(true); + } + + @Override + public boolean isThisProfileSet(Profile profile) { + if (!isInitialized()) { + /* This might be called too soon during boot. Return true to prevent a request + to update the profile. KeepAlive is called every Constants.keepalivems + and will detect the need for a profile update and apply it. + */ + return true; + } + return pump.basalProfile.equals(convertProfileToComboProfile(profile)); + } + + @NonNull + private BasalProfile convertProfileToComboProfile(Profile profile) { + BasalProfile basalProfile = new BasalProfile(); + for (int i = 0; i < 24; i++) { + double rate = profile.getBasalTimeFromMidnight(i * 60 * 60); + + /*The Combo pump does hava a different granularity for basal rate: + * 0.01 - if below 1U/h + * 0.05 - if above 1U/h + * */ + + if (rate < 1) { + //round to 0.01 granularity; + rate = Math.round(rate / 0.01) * 0.01; + } else { + //round to 0.05 granularity; + rate = Math.round(rate / 0.05) * 0.05; + } + + basalProfile.hourlyRates[i] = rate; + } + return basalProfile; + } + + @NonNull + @Override + public Date lastDataTime() { + return new Date(pump.lastSuccessfulCmdTime); + } + + /** + * Runs pump initialization if needed and reads the pump state from the main screen. + */ + @Override + public synchronized void getPumpStatus() { + log.debug("getPumpStatus called"); + if (!pump.initialized) { + initializePump(); + } else { + // trigger a connect, which will update state and check history + runCommand(null, 3, ruffyScripter::readPumpState); + } + } + + private synchronized void initializePump() { + long maxWait = System.currentTimeMillis() + 15 * 1000; + while (!ruffyScripter.isPumpAvailable()) { + log.debug("Waiting for ruffy service to come up ..."); + SystemClock.sleep(100); + if (System.currentTimeMillis() > maxWait) { + log.debug("ruffy service unavailable, wtf"); + return; + } + } + + // trigger a connect, which will update state and check history + CommandResult stateResult = runCommand(null, 1, ruffyScripter::readPumpState); + if (!stateResult.success) { + return; + } + + // note that since the history is checked upon every connect, the above already updated + // the DB with any changed history records + if (pumpHistoryChanged) { + log.debug("Pump history has changed and was imported"); + pumpHistoryChanged = false; + } + + if (stateResult.state.unsafeUsageDetected == PumpState.UNSUPPORTED_BASAL_RATE_PROFILE) { + Notification n = new Notification(Notification.COMBO_PUMP_ALARM, + MainApp.gs(R.string.combo_force_disabled_notification), + Notification.URGENT); + n.soundId = R.raw.alarm; + MainApp.bus().post(new EventNewNotification(n)); + return; + } + + // read basal profile into cache (KeepAlive will trigger a profile update if needed) + CommandResult readBasalResult = runCommand(MainApp.gs(R.string.combo_actvity_reading_basal_profile), 2, ruffyScripter::readBasalProfile); + if (!readBasalResult.success) { + return; + } + pump.basalProfile = readBasalResult.basalProfile; + setValidBasalRateProfileSelectedOnPump(true); + + pump.initialized = true; + MainApp.bus().post(new EventInitializationChanged()); + + // show notification to check pump date if last bolus is older than 24 hours + // or is in the future + if (!recentBoluses.isEmpty()) { + long lastBolusTimestamp = recentBoluses.get(0).timestamp; + long now = System.currentTimeMillis(); + if (lastBolusTimestamp < now - 24 * 60 * 60 * 1000 || lastBolusTimestamp > now + 5 * 60 * 1000) { + Notification notification = new Notification(Notification.COMBO_PUMP_ALARM, MainApp.gs(R.string.combo_check_date), Notification.URGENT); + MainApp.bus().post(new EventNewNotification(notification)); + } + } + + // ComboFragment updates state fully only after the pump has initialized, + // so force an update after initialization completed + MainApp.bus().post(new EventComboPumpUpdateGUI()); + } + + /** + * Updates local cache with state (reservoir level, last bolus ...) returned from the pump + */ + private void updateLocalData(CommandResult result) { + if (result.reservoirLevel != PumpState.UNKNOWN) { + pump.reservoirLevel = result.reservoirLevel; + } + if (result.history != null && !result.history.bolusHistory.isEmpty()) { + pump.lastBolus = result.history.bolusHistory.get(0); + } + if (result.state.menu != null) { + pump.state = result.state; + } + MainApp.bus().post(new EventComboPumpUpdateGUI()); + } + + @Override + public double getBaseBasalRate() { + int currentHour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY); + return pump.basalProfile.hourlyRates[currentHour]; + } + + private static BolusProgressReporter nullBolusProgressReporter = (state, percent, delivered) -> { + }; + + private static BolusProgressReporter bolusProgressReporter = (state, percent, delivered) -> { + EventOverviewBolusProgress event = EventOverviewBolusProgress.getInstance(); + switch (state) { + case PROGRAMMING: + event.status = MainApp.gs(R.string.combo_programming_bolus); + break; + case DELIVERING: + event.status = MainApp.gs(R.string.bolusdelivering, delivered); + break; + case DELIVERED: + event.status = MainApp.gs(R.string.bolusdelivered, delivered); + break; + case STOPPING: + event.status = MainApp.gs(R.string.bolusstopping); + break; + case STOPPED: + event.status = MainApp.gs(R.string.bolusstopped); + break; + } + event.percent = percent; + MainApp.bus().post(event); + }; + + /** + * Updates Treatment records with carbs and boluses and delivers a bolus if needed + */ + @Override + public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { + try { + if (detailedBolusInfo.insulin == 0 && detailedBolusInfo.carbs == 0) { + // neither carbs nor bolus requested + log.error("deliverTreatment: Invalid input"); + return new PumpEnactResult().success(false).enacted(false) + .bolusDelivered(0d).carbsDelivered(0d) + .comment(MainApp.instance().getString(R.string.danar_invalidinput)); + } else if (detailedBolusInfo.insulin > 0) { + // bolus needed, ask pump to deliver it + return deliverBolus(detailedBolusInfo); + } else { + // no bolus required, carb only treatment + TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo); + + EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); + bolusingEvent.t = new Treatment(); + bolusingEvent.t.isSMB = detailedBolusInfo.isSMB; + bolusingEvent.percent = 100; + MainApp.bus().post(bolusingEvent); + + return new PumpEnactResult().success(true).enacted(true) + .bolusDelivered(0d).carbsDelivered(detailedBolusInfo.carbs) + .comment(MainApp.instance().getString(R.string.virtualpump_resultok)); + } + } finally { + MainApp.bus().post(new EventComboPumpUpdateGUI()); + } + } + + @NonNull + private PumpEnactResult deliverBolus(final DetailedBolusInfo detailedBolusInfo) { + try { + pump.activity = MainApp.gs(R.string.combo_pump_action_bolusing, detailedBolusInfo.insulin); + MainApp.bus().post(new EventComboPumpUpdateGUI()); + + // check pump is ready and all pump bolus records are known + CommandResult stateResult = runCommand(null, 2, () -> ruffyScripter.readQuickInfo(1)); + if (!stateResult.success) { + return new PumpEnactResult().success(false).enacted(false) + .comment(MainApp.gs(R.string.combo_error_no_connection_no_bolus_delivered)); + } + if (stateResult.reservoirLevel != -1 && stateResult.reservoirLevel - 0.5 < detailedBolusInfo.insulin) { + return new PumpEnactResult().success(false).enacted(false) + .comment(MainApp.gs(R.string.combo_reservoir_level_insufficient_for_bolus)); + } + // the commands above ensured a connection was made, which updated this field + if (pumpHistoryChanged) { + return new PumpEnactResult().success(false).enacted(false) + .comment(MainApp.gs(R.string.combo_bolus_rejected_due_to_pump_history_change)); + } + + Bolus previousBolus = stateResult.history != null && !stateResult.history.bolusHistory.isEmpty() + ? stateResult.history.bolusHistory.get(0) + : new Bolus(0, 0, false); + + // reject a bolus if one with the exact same size was successfully delivered + // within the last 1-2 minutes + if (Math.abs(previousBolus.amount - detailedBolusInfo.insulin) < 0.01 + && previousBolus.timestamp + 60 * 1000 > System.currentTimeMillis()) { + log.debug("Bolu request rejected, same bolus was successfully delivered very recently"); + return new PumpEnactResult().success(false).enacted(false) + .comment(MainApp.gs(R.string.bolus_frequency_exceeded)); + } + + // if the last bolus was given in the current minute, wait till the pump clock moves + // to the next minute to ensure timestamps are unique and can be imported + CommandResult timeCheckResult = stateResult; + long waitStartTime = System.currentTimeMillis(); + long maxWaitTimeout = waitStartTime + 65 * 1000; + int waitLoops = 0; + while (previousBolus.timestamp == timeCheckResult.state.pumpTime + && maxWaitTimeout > System.currentTimeMillis()) { + if (cancelBolus) { + return new PumpEnactResult().success(true).enacted(false); + } + if (!timeCheckResult.success) { + return new PumpEnactResult().success(false).enacted(false) + .comment(MainApp.gs(R.string.combo_error_no_connection_no_bolus_delivered)); + } + log.debug("Waiting for pump clock to advance for the next unused bolus record timestamp"); + SystemClock.sleep(2000); + timeCheckResult = runCommand(null, 0, ruffyScripter::readPumpState); + waitLoops++; + } + if (waitLoops > 0) { + long waitDuration = (System.currentTimeMillis() - waitStartTime) / 1000; + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboBolusTimestampWait") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION) + .putCustomAttribute("waitTimeSecs", String.valueOf(waitDuration))); + log.debug("Waited " + waitDuration + "s for pump to switch to a fresh minute before bolusing"); + } + + if (cancelBolus) { + return new PumpEnactResult().success(true).enacted(false); + } + + BolusProgressReporter progressReporter = detailedBolusInfo.isSMB ? nullBolusProgressReporter : bolusProgressReporter; + + // start bolus delivery + scripterIsBolusing = true; + runCommand(null, 0, + () -> ruffyScripter.deliverBolus(detailedBolusInfo.insulin, progressReporter)); + scripterIsBolusing = false; + + // Note that the result of the issued bolus command is not checked. If there was + // a connection problem, ruffyscripter tried to recover and we can just check the + // history below to see what was actually delivered + + // get last bolus from pump history for verification + // (reads 2 records to update `recentBoluses` further down) + CommandResult postBolusStateResult = runCommand(null, 3, () -> ruffyScripter.readQuickInfo(2)); + if (!postBolusStateResult.success) { + return new PumpEnactResult().success(false).enacted(false) + .comment(MainApp.gs(R.string.combo_error_bolus_verification_failed)); + } + Bolus lastPumpBolus = postBolusStateResult.history != null && !postBolusStateResult.history.bolusHistory.isEmpty() + ? postBolusStateResult.history.bolusHistory.get(0) + : null; + + // no bolus delivered? + if (lastPumpBolus == null || lastPumpBolus.equals(previousBolus)) { + if (cancelBolus) { + return new PumpEnactResult().success(true).enacted(false); + } else { + return new PumpEnactResult() + .success(false) + .enacted(false) + .comment(MainApp.gs(R.string.combo_error_no_bolus_delivered)); + } + } + + // at least some insulin delivered, so add it to treatments + if (!addBolusToTreatments(detailedBolusInfo, lastPumpBolus)) + return new PumpEnactResult().success(false).enacted(true) + .comment(MainApp.gs(R.string.combo_error_updating_treatment_record)); + + // check pump bolus record has a sane timestamp + long now = System.currentTimeMillis(); + if (lastPumpBolus.timestamp < now - 10 * 60 * 1000 || lastPumpBolus.timestamp > now + 10 * 60 * 1000) { + Notification notification = new Notification(Notification.COMBO_PUMP_ALARM, MainApp.gs(R.string.combo_suspious_bolus_time), Notification.URGENT); + MainApp.bus().post(new EventNewNotification(notification)); + } + + // update `recentBoluses` so the bolus was just delivered won't be detected as a new + // bolus that has been delivered on the pump + recentBoluses = postBolusStateResult.history.bolusHistory; + + // only a partial bolus was delivered + if (Math.abs(lastPumpBolus.amount - detailedBolusInfo.insulin) > 0.01) { + if (cancelBolus) { + return new PumpEnactResult().success(true).enacted(true); + } + return new PumpEnactResult().success(false).enacted(true) + .comment(MainApp.gs(R.string.combo_error_partial_bolus_delivered, + lastPumpBolus.amount, detailedBolusInfo.insulin)); + } + + // full bolus was delivered successfully + incrementBolusCount(); + return new PumpEnactResult() + .success(true) + .enacted(lastPumpBolus.amount > 0) + .bolusDelivered(lastPumpBolus.amount) + .carbsDelivered(detailedBolusInfo.carbs); + } finally { + pump.activity = null; + MainApp.bus().post(new EventComboPumpUpdateGUI()); + MainApp.bus().post(new EventRefreshOverview("Bolus")); + cancelBolus = false; + } + } + + private void incrementTbrCount() { + try { + SP.putLong(COMBO_TBRS_SET, SP.getLong(COMBO_TBRS_SET, 0L) + 1); + } catch (Exception e) { + // ignore + } + } + + private void incrementBolusCount() { + try { + SP.putLong(COMBO_BOLUSES_DELIVERED, SP.getLong(COMBO_BOLUSES_DELIVERED, 0L) + 1); + } catch (Exception e) { + // ignore + } + } + + /** + * Updates a DetailedBolusInfo from a pump bolus and adds it as a Treatment to the DB. + * Handles edge cases when dates aren't unique which are extremely unlikely to occur, + * but if they do, the user should be warned since a bolus will be missing from calculations. + */ + private boolean addBolusToTreatments(DetailedBolusInfo detailedBolusInfo, Bolus lastPumpBolus) { + DetailedBolusInfo dbi = detailedBolusInfo.copy(); + dbi.date = calculateFakeBolusDate(lastPumpBolus); + dbi.pumpId = dbi.date; + dbi.source = Source.PUMP; + dbi.insulin = lastPumpBolus.amount; + try { + boolean treatmentCreated = TreatmentsPlugin.getPlugin().addToHistoryTreatment(dbi); + if (!treatmentCreated) { + log.error("Adding treatment record overrode an existing record: " + dbi); + if (dbi.isSMB) { + Notification notification = new Notification(Notification.COMBO_PUMP_ALARM, MainApp.gs(R.string.combo_error_updating_treatment_record), Notification.URGENT); + MainApp.bus().post(new EventNewNotification(notification)); + } + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboBolusToDbError") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION) + .putCustomAttribute("bolus", String.valueOf(lastPumpBolus.amount)) + .putCustomAttribute("issue", "record with same timestamp existed and was overridden")); + return false; + } + } catch (Exception e) { + log.error("Adding treatment record failed", e); + if (dbi.isSMB) { + Notification notification = new Notification(Notification.COMBO_PUMP_ALARM, MainApp.gs(R.string.combo_error_updating_treatment_record), Notification.URGENT); + MainApp.bus().post(new EventNewNotification(notification)); + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboBolusToDbError") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION) + .putCustomAttribute("bolus", String.valueOf(lastPumpBolus.amount)) + .putCustomAttribute("issue", "adding record caused exception")); + } + return false; + } + return true; + } + + @Override + public void stopBolusDelivering() { + if (scripterIsBolusing) { + ruffyScripter.cancelBolus(); + } + cancelBolus = true; + } + + /** + * Note: AAPS calls this solely to enact OpenAPS suggestions + * + * @param force the force parameter isn't used currently since we always set the tbr - + * there might be room for optimization to first test the currently running tbr + * and only change it if it differs (as the DanaR plugin does). This approach + * might have other issues though (what happens if the tbr which wasn't re-set to + * the new value (and thus still has the old duration of e.g. 1 min) expires?) + */ + @Override + public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, Profile profile, boolean force) { + log.debug("setTempBasalAbsolute called with a rate of " + absoluteRate + " for " + durationInMinutes + " min."); + int unroundedPercentage = Double.valueOf(absoluteRate / getBaseBasalRate() * 100).intValue(); + int roundedPercentage = (int) (Math.round(absoluteRate / getBaseBasalRate() * 10) * 10); + if (unroundedPercentage != roundedPercentage) { + log.debug("Rounded requested rate " + unroundedPercentage + "% -> " + roundedPercentage + "%"); + } + + return setTempBasalPercent(roundedPercentage, durationInMinutes); + } + + /** + * Note: AAPS calls this directly only for setting a temp basal issued by the user + * + * @param forceNew Driver always applies the requested TBR and simply overrides whatever TBR + * is or isn't running at the moment + */ + @Override + public PumpEnactResult setTempBasalPercent(Integer percent, final Integer durationInMinutes, Profile profile, boolean forceNew) { + return setTempBasalPercent(percent, durationInMinutes); + } + + private PumpEnactResult setTempBasalPercent(Integer percent, final Integer durationInMinutes) { + log.debug("setTempBasalPercent called with " + percent + "% for " + durationInMinutes + "min"); + + if (pumpHistoryChanged && percent > 110) { + return new PumpEnactResult().success(false).enacted(false) + .comment(MainApp.gs(R.string.combo_high_temp_rejected_due_to_pump_history_changes)); + } + + int adjustedPercent = percent; + + if (adjustedPercent > pumpDescription.maxTempPercent) { + log.debug("Reducing requested TBR to the maximum support by the pump: " + percent + " -> " + pumpDescription.maxTempPercent); + adjustedPercent = pumpDescription.maxTempPercent; + } + + if (adjustedPercent % 10 != 0) { + Long rounded = Math.round(adjustedPercent / 10d) * 10; + log.debug("Rounded requested percentage:" + adjustedPercent + " -> " + rounded); + adjustedPercent = rounded.intValue(); + } + + // do a soft TBR-cancel when requested rate was rounded to 100% (>94% && <104%) + if (adjustedPercent == 100) { + return cancelTempBasal(false); + } + + int finalAdjustedPercent = adjustedPercent; + CommandResult commandResult = runCommand(MainApp.gs(R.string.combo_pump_action_setting_tbr, percent, durationInMinutes), + 3, () -> ruffyScripter.setTbr(finalAdjustedPercent, durationInMinutes)); + if (!commandResult.success) { + return new PumpEnactResult().success(false).enacted(false); + } + + PumpState state = commandResult.state; + if (state.tbrActive && state.tbrPercent == adjustedPercent + && (state.tbrRemainingDuration == durationInMinutes || state.tbrRemainingDuration == durationInMinutes - 1)) { + TemporaryBasal tempStart = new TemporaryBasal() + .date(state.timestamp) + .duration(state.tbrRemainingDuration) + .percent(state.tbrPercent) + .source(Source.USER); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempStart); + + MainApp.bus().post(new EventComboPumpUpdateGUI()); + } + + incrementTbrCount(); + return new PumpEnactResult().success(true).enacted(true).isPercent(true) + .percent(state.tbrPercent).duration(state.tbrRemainingDuration); + } + + @Override + public PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes) { + return OPERATION_NOT_SUPPORTED; + } + + /** + * Cancel an active Temp Basal. Mostly sets a fake Temp Basal to avoid a TBR CANCELLED + * alert. This relies on TemporaryBasal objects to properly reflect the pumps state, + * which is ensured by {@link #checkAndResolveTbrMismatch(PumpState)}, which runs on each + * connect. When a hard cancel is requested, the pump is queried for it's TBR state to + * make absolutely sure no TBR is running (such a request is also made when resuming the + * loop, irregardless of whether a TBR is running or not). + */ + @Override + public PumpEnactResult cancelTempBasal(boolean enforceNew) { + log.debug("cancelTempBasal called"); + final TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); + if (enforceNew) { + CommandResult stateResult = runCommand(MainApp.gs(R.string.combo_pump_action_refreshing), 2, ruffyScripter::readPumpState); + if (!stateResult.success) { + return new PumpEnactResult().success(false).enacted(false); + } + if (!stateResult.state.tbrActive) { + return new PumpEnactResult().success(true).enacted(false); + } + log.debug("cancelTempBasal: hard-cancelling TBR since force requested"); + CommandResult cancelResult = runCommand(MainApp.gs(R.string.combo_pump_action_cancelling_tbr), 2, ruffyScripter::cancelTbr); + if (!cancelResult.success) { + return new PumpEnactResult().success(false).enacted(false); + } + if (!cancelResult.state.tbrActive) { + TemporaryBasal tempBasal = new TemporaryBasal() + .date(cancelResult.state.timestamp) + .duration(0) + .source(Source.USER); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempBasal); + return new PumpEnactResult().isTempCancel(true).success(true).enacted(true); + } else { + return new PumpEnactResult().success(false).enacted(false); + } + } else if (activeTemp == null) { + return new PumpEnactResult().success(true).enacted(false); + } else if ((activeTemp.percentRate >= 90 && activeTemp.percentRate <= 110) && activeTemp.getPlannedRemainingMinutes() <= 15) { + // Let fake neutral temp keep run (see below) + // Note that since this runs on the queue a connection is opened regardless, but this + // case doesn't occur all that often, so it's not worth optimizing (1.3k SetTBR vs 4 cancelTBR). + log.debug("cancelTempBasal: skipping changing tbr since it already is at " + activeTemp.percentRate + "% and running for another " + activeTemp.getPlannedRemainingMinutes() + " mins."); + return new PumpEnactResult().success(true).enacted(true) + .comment("cancelTempBasal skipping changing tbr since it already is at " + + activeTemp.percentRate + "% and running for another " + + activeTemp.getPlannedRemainingMinutes() + " mins."); + } else { + // Set a fake neutral temp to avoid TBR cancel alert. Decide 90% vs 110% based on + // on whether the TBR we're cancelling is above or below 100%. + final int percentage = (activeTemp.percentRate > 100) ? 110 : 90; + log.debug("cancelTempBasal: changing TBR to " + percentage + "% for 15 mins."); + return setTempBasalPercent(percentage, 15); + } + } + + private interface CommandExecution { + CommandResult execute(); + } + + /** + * Runs a command, sets an activity if provided, retries if requested and updates fields + * concerned with last connection. + * Local cache (history, reservoir level, pump state) are updated via #updateLocalData() + * if returned by a command. + */ + private synchronized CommandResult runCommand(String activity, int retries, CommandExecution commandExecution) { + CommandResult commandResult; + try { + if (!ruffyScripter.isConnected()) { + String originalActivity = pump.activity; + pump.activity = MainApp.gs(R.string.combo_activity_checking_pump_state); + MainApp.bus().post(new EventComboPumpUpdateGUI()); + CommandResult preCheckError = runOnConnectChecks(); + pump.activity = originalActivity; + if (preCheckError != null) { + updateLocalData(preCheckError); + return preCheckError; + } + } + + if (activity != null) { + pump.activity = activity; + MainApp.bus().post(new EventComboPumpUpdateGUI()); + } + + commandResult = commandExecution.execute(); + + if (!commandResult.success && retries > 0) { + for (int retryAttempts = 1; !commandResult.success && retryAttempts <= retries; retryAttempts++) { + log.debug("Command was not successful, retries requested, doing retry #" + retryAttempts); + commandResult = commandExecution.execute(); + } + } + + for (Integer forwardedWarning : commandResult.forwardedWarnings) { + notifyAboutPumpWarning(new WarningOrErrorCode(forwardedWarning, null, null)); + } + + if (commandResult.success) { + pump.lastSuccessfulCmdTime = System.currentTimeMillis(); + if (validBasalRateProfileSelectedOnPump && commandResult.state.unsafeUsageDetected == PumpState.UNSUPPORTED_BASAL_RATE_PROFILE) { + setValidBasalRateProfileSelectedOnPump(false); + Notification n = new Notification(Notification.COMBO_PUMP_ALARM, + MainApp.gs(R.string.combo_force_disabled_notification), + Notification.URGENT); + n.soundId = R.raw.alarm; + MainApp.bus().post(new EventNewNotification(n)); + ConfigBuilderPlugin.getCommandQueue().cancelTempBasal(true, null); + } + updateLocalData(commandResult); + } + } finally { + if (activity != null) { + pump.activity = null; + MainApp.bus().post(new EventComboPumpUpdateGUI()); + } + } + + return commandResult; + } + + public void setValidBasalRateProfileSelectedOnPump(boolean value) { + validBasalRateProfileSelectedOnPump = value; + } + + /** + * Returns the command result of running ReadPumpState if it wasn't successful, indicating + * an error condition. Returns null otherwise. + */ + private CommandResult runOnConnectChecks() { + CommandResult preCheckResult = ruffyScripter.readPumpState(); + if (!preCheckResult.success) { + return preCheckResult; + } + + WarningOrErrorCode activeAlert = preCheckResult.state.activeAlert; + // note if multiple alerts are active this will and should fail; e.g. if pump was stopped + // due to empty cartridge alert, which might also trigger TBR cancelled alert + if (activeAlert != null) { + if (activeAlert.warningCode != null + && (activeAlert.warningCode == PumpWarningCodes.CARTRIDGE_LOW || + activeAlert.warningCode == PumpWarningCodes.BATTERY_LOW || + activeAlert.warningCode == PumpWarningCodes.TBR_CANCELLED)) { + // turn benign warnings into notifications + notifyAboutPumpWarning(activeAlert); + ruffyScripter.confirmAlert(activeAlert.warningCode); + } else if (activeAlert.errorCode != null) { + Notification notification = new Notification(); + notification.date = new Date(); + notification.id = Notification.COMBO_PUMP_ALARM; + notification.level = Notification.URGENT; + notification.text = MainApp.gs(R.string.combo_is_in_error_state, activeAlert.errorCode, activeAlert.message); + MainApp.bus().post(new EventNewNotification(notification)); + return preCheckResult.success(false); + } + } + + if (!preCheckResult.state.suspended) { + checkForUnsafeUsage(preCheckResult); + checkAndResolveTbrMismatch(preCheckResult.state); + checkPumpTime(preCheckResult.state); + checkBasalRate(preCheckResult.state); + CommandResult historyCheckError = checkHistory(); + if (historyCheckError != null) { + return historyCheckError; + } + } else { + long now = System.currentTimeMillis(); + TemporaryBasal aapsTbr = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(now); + if (aapsTbr == null || aapsTbr.percentRate != 0) { + log.debug("Creating 15m zero temp since pump is suspended"); + TemporaryBasal newTempBasal = new TemporaryBasal() + .date(now) + .percent(0) + .duration(15) + .source(Source.USER); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(newTempBasal); + } + } + + return null; + } + + + private void checkBasalRate(PumpState state) { + if (!pump.initialized) { + // no cached profile to compare against + return; + } + if (state.unsafeUsageDetected != PumpState.SAFE_USAGE) { + // with an extended or multiwavo bolus running it's not (easily) possible + // to infer base basal rate and not supported either. Also don't compare + // if set basal rate profile is != -1. + return; + } + if (state.tbrActive && state.tbrPercent == 0) { + // can't infer base basal rate if TBR is 0 + return; + } + double pumpBasalRate = state.tbrActive + ? Math.round(state.basalRate * 100 / state.tbrPercent * 100) / 100d + : state.basalRate; + int pumpHour = new Date(state.pumpTime).getHours(); + int phoneHour = new Date().getHours(); + if (pumpHour != phoneHour) { + // only check if clocks are close + return; + } + + if (Math.abs(pumpBasalRate - getBaseBasalRate()) > 0.001) { + CommandResult readBasalResult = runCommand(MainApp.gs(R.string.combo_actvity_reading_basal_profile), 2, ruffyScripter::readBasalProfile); + if (readBasalResult.success) { + pump.basalProfile = readBasalResult.basalProfile; + Notification notification = new Notification(Notification.COMBO_PUMP_ALARM, MainApp.gs(R.string.combo_warning_pump_basal_rate_changed), Notification.NORMAL); + MainApp.bus().post(new EventNewNotification(notification)); + } else { + Notification notification = new Notification(Notification.COMBO_PUMP_ALARM, MainApp.gs(R.string.combo_error_failure_reading_changed_basal_rate), Notification.URGENT); + MainApp.bus().post(new EventNewNotification(notification)); + } + } + } + + /** + * Check pump time (on the main menu) and raise notification if time is off. + * (setting clock is not supported by ruffy) + */ + private void checkPumpTime(PumpState state) { + if (state.pumpTime == 0) { + // time couldn't be read (e.g. a warning is displayed on the menu , hiding the time field) + } else if (Math.abs(state.pumpTime - System.currentTimeMillis()) >= 10 * 60 * 1000) { + log.debug("Pump clock needs update, pump time: " + state.pumpTime + " (" + new Date(state.pumpTime) + ")"); + Notification notification = new Notification(Notification.COMBO_PUMP_ALARM, MainApp.gs(R.string.combo_notification_check_time_date), Notification.URGENT); + MainApp.bus().post(new EventNewNotification(notification)); + } else if (Math.abs(state.pumpTime - System.currentTimeMillis()) >= 3 * 60 * 1000) { + log.debug("Pump clock needs update, pump time: " + state.pumpTime + " (" + new Date(state.pumpTime) + ")"); + Notification notification = new Notification(Notification.COMBO_PUMP_ALARM, MainApp.gs(R.string.combo_notification_check_time_date), Notification.NORMAL); + MainApp.bus().post(new EventNewNotification(notification)); + } + } + + private void notifyAboutPumpWarning(WarningOrErrorCode activeAlert) { + if (activeAlert.warningCode == null || + (!activeAlert.warningCode.equals(PumpWarningCodes.CARTRIDGE_LOW) + && !activeAlert.warningCode.equals(PumpWarningCodes.BATTERY_LOW) + && !activeAlert.warningCode.equals(PumpWarningCodes.TBR_CANCELLED))) { + throw new IllegalArgumentException(activeAlert.toString()); + } + Notification notification = new Notification(); + notification.date = new Date(); + notification.id = Notification.COMBO_PUMP_ALARM; + notification.level = Notification.NORMAL; + if (activeAlert.warningCode == PumpWarningCodes.CARTRIDGE_LOW) { + notification.text = MainApp.gs(R.string.combo_pump_cartridge_low_warrning); + } else if (activeAlert.warningCode == PumpWarningCodes.BATTERY_LOW) { + notification.text = MainApp.gs(R.string.combo_pump_battery_low_warrning); + } else if (activeAlert.warningCode == PumpWarningCodes.TBR_CANCELLED) { + notification.text = MainApp.gs(R.string.combo_pump_tbr_cancelled_warrning); + } + MainApp.bus().post(new EventNewNotification(notification)); + } + + private void checkForUnsafeUsage(CommandResult commandResult) { + if (commandResult == null) return; + + long lastViolation = 0; + if (commandResult.state.unsafeUsageDetected == PumpState.UNSUPPORTED_BOLUS_TYPE) { + lastViolation = System.currentTimeMillis(); + } else if (commandResult.history != null) { + for (Bolus bolus : commandResult.history.bolusHistory) { + if (!bolus.isValid && bolus.timestamp > lastViolation) { + lastViolation = bolus.timestamp; + } + } + } + if (lastViolation > 0) { + lowSuspendOnlyLoopEnforcedUntil = lastViolation + 6 * 60 * 60 * 1000; + if (lowSuspendOnlyLoopEnforcedUntil > System.currentTimeMillis() && violationWarningRaisedForBolusAt != lowSuspendOnlyLoopEnforcedUntil) { + Notification n = new Notification(Notification.COMBO_PUMP_ALARM, + MainApp.gs(R.string.combo_low_suspend_forced_notification), + Notification.URGENT); + n.soundId = R.raw.alarm; + MainApp.bus().post(new EventNewNotification(n)); + violationWarningRaisedForBolusAt = lowSuspendOnlyLoopEnforcedUntil; + ConfigBuilderPlugin.getCommandQueue().cancelTempBasal(true, null); + } + } + } + + /** + * Checks the main screen to determine if TBR on pump matches app state. + */ + private void checkAndResolveTbrMismatch(PumpState state) { + // compare with: info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgStatusTempBasal.updateTempBasalInDB() + long now = System.currentTimeMillis(); + TemporaryBasal aapsTbr = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(now); + if (aapsTbr == null && state.tbrActive && state.tbrRemainingDuration > 2) { + log.debug("Creating temp basal from pump TBR"); + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboTbrMismatch") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION) + .putCustomAttribute("type", "new TBR on pump")); + TemporaryBasal newTempBasal = new TemporaryBasal() + .date(now) + .percent(state.tbrPercent) + .duration(state.tbrRemainingDuration) + .source(Source.USER); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(newTempBasal); + } else if (aapsTbr != null && aapsTbr.getPlannedRemainingMinutes() > 2 && !state.tbrActive) { + log.debug("Ending AAPS-TBR since pump has no TBR active"); + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboTbrMismatch") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION) + .putCustomAttribute("type", "TBR cancelled on pump")); + TemporaryBasal tempStop = new TemporaryBasal() + .date(now) + .duration(0) + .source(Source.USER); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempStop); + } else if (aapsTbr != null && state.tbrActive + && (aapsTbr.percentRate != state.tbrPercent || + Math.abs(aapsTbr.getPlannedRemainingMinutes() - state.tbrRemainingDuration) > 2)) { + log.debug("AAPSs and pump-TBR differ; ending AAPS-TBR and creating new TBR based on pump TBR"); + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboTbrMismatch") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION) + .putCustomAttribute("type", "TBR on pump differs from AAPS TBR")); + TemporaryBasal tempStop = new TemporaryBasal() + .date(now - 1000) + .duration(0) + .source(Source.USER); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempStop); + + TemporaryBasal newTempBasal = new TemporaryBasal() + .date(now) + .percent(state.tbrPercent) + .duration(state.tbrRemainingDuration) + .source(Source.USER); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(newTempBasal); + } + } + + /** + * Reads the pump's history and updates the DB accordingly. + */ + private boolean readHistory(@Nullable PumpHistoryRequest request) { + CommandResult historyResult = runCommand(MainApp.gs(R.string.combo_activity_reading_pump_history), 3, () -> ruffyScripter.readHistory(request)); + PumpHistory history = historyResult.history; + if (!historyResult.success || history == null) { + return false; + } + + updateDbFromPumpHistory(history); + + // update local cache + if (!history.pumpAlertHistory.isEmpty()) { + pump.errorHistory = history.pumpAlertHistory; + } + if (!history.tddHistory.isEmpty()) { + pump.tddHistory = history.tddHistory; + } + + return historyResult.success; + } + + private boolean updateDbFromPumpHistory(@NonNull PumpHistory history) { + boolean updated = false; + for (Bolus pumpBolus : history.bolusHistory) { + DetailedBolusInfo dbi = new DetailedBolusInfo(); + dbi.date = calculateFakeBolusDate(pumpBolus); + dbi.pumpId = dbi.date; + dbi.source = Source.PUMP; + dbi.insulin = pumpBolus.amount; + dbi.eventType = CareportalEvent.CORRECTIONBOLUS; + if (TreatmentsPlugin.getPlugin().addToHistoryTreatment(dbi)) { + updated = true; + } + } + return updated; + } + + /** + * Adds the bolus to the timestamp to be able to differentiate multiple boluses in the same + * minute. Best effort, since this covers only boluses up to 6.0 U and relies on other code + * to prevent a boluses with the same amount to be delivered within the same minute. + * Should be good enough, even with command mode, it's a challenge to create that situation + * and most time clashes will be around SMBs which are covered. + */ + long calculateFakeBolusDate(Bolus pumpBolus) { + double bolus = pumpBolus.amount - 0.1; + int secondsFromBolus = (int) (bolus * 10 * 1000); + return pumpBolus.timestamp + Math.min(secondsFromBolus, 59 * 1000); + } + + /** + * Reads QuickInfo to update reservoir level and determine if new boluses exist on the pump + * and if so, queries the history for all new records. + * + * @return null on success or the failed command result + */ + private CommandResult checkHistory() { + CommandResult quickInfoResult = runCommand(MainApp.gs(R.string.combo_activity_checking_for_history_changes), 3, + () -> ruffyScripter.readQuickInfo(2)); + + // no history, nothing to check or complain about + if (quickInfoResult.history == null || quickInfoResult.history.bolusHistory.isEmpty()) { + log.debug("Setting 'pumpHistoryChanged' false"); + pumpHistoryChanged = false; + return null; + } + + // compare recent records + List initialPumpBolusHistory = quickInfoResult.history.bolusHistory; + if (recentBoluses.size() == 1 && initialPumpBolusHistory.size() >= 1 + && recentBoluses.get(0).equals(quickInfoResult.history.bolusHistory.get(0))) { + log.debug("Setting 'pumpHistoryChanged' false"); + pumpHistoryChanged = false; + return null; + } else if (recentBoluses.size() == 2 && initialPumpBolusHistory.size() >= 2 + && recentBoluses.get(0).equals(quickInfoResult.history.bolusHistory.get(0)) + && recentBoluses.get(1).equals(quickInfoResult.history.bolusHistory.get(1))) { + log.debug("Setting 'pumpHistoryChanged' false"); + pumpHistoryChanged = false; + return null; + } + + // fetch new records + long lastKnownPumpRecordTimestamp = recentBoluses.isEmpty() ? 0 : recentBoluses.get(0).timestamp; + CommandResult historyResult = runCommand(MainApp.gs(R.string.combo_activity_reading_pump_history), 3, () -> + ruffyScripter.readHistory(new PumpHistoryRequest().bolusHistory(lastKnownPumpRecordTimestamp))); + if (!historyResult.success) { + pumpHistoryChanged = true; + return historyResult; + } + + // Check edge of multiple boluses with the same amount in the same minute being imported. + // This is about as edgy-casey as it can get. I'd be surprised of this one actually ever + // triggers. It might, so at least give a warning, since a delivered bolus isn't accounted + // for. + HashSet bolusSet = new HashSet<>(historyResult.history.bolusHistory); + if (bolusSet.size() != historyResult.history.bolusHistory.size()) { + log.debug("Bolus with same amount within the same minute imported. Only one will make it to the DB."); + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboBolusToDbError") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION) + .putCustomAttribute("bolus", "") + .putCustomAttribute("issue", "multiple pump history records with the same time and amount")); + Notification notification = new Notification(Notification.COMBO_PUMP_ALARM, MainApp.gs(R.string. + combo_error_multiple_boluses_with_identical_timestamp), Notification.URGENT); + MainApp.bus().post(new EventNewNotification(notification)); + } + + pumpHistoryChanged = updateDbFromPumpHistory(historyResult.history); + if (pumpHistoryChanged) { + log.debug("Setting 'pumpHistoryChanged' true"); + } + + List updatedPumpBolusHistory = historyResult.history.bolusHistory; + if (!updatedPumpBolusHistory.isEmpty()) { + recentBoluses = updatedPumpBolusHistory.subList(0, Math.min(updatedPumpBolusHistory.size(), 2)); + } + + return null; + } + + @Override + public PumpEnactResult cancelExtendedBolus() { + return OPERATION_NOT_SUPPORTED; + } + + @Override + public JSONObject getJSONStatus(Profile profile, String profileName) { + if (!pump.initialized) { + return null; + } + + try { + JSONObject pumpJson = new JSONObject(); + pumpJson.put("clock", DateUtil.toISOString(pump.lastSuccessfulCmdTime)); + + int level; + if (pump.reservoirLevel != -1) level = pump.reservoirLevel; + else if (pump.state.insulinState == PumpState.LOW) level = 8; + else if (pump.state.insulinState == PumpState.EMPTY) level = 0; + else level = 150; + pumpJson.put("reservoir", level); + + JSONObject statusJson = new JSONObject(); + statusJson.put("status", getStateSummary()); + statusJson.put("timestamp", pump.lastSuccessfulCmdTime); + pumpJson.put("status", statusJson); + + JSONObject extendedJson = new JSONObject(); + extendedJson.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION); + extendedJson.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName()); + PumpState ps = pump.state; + if (ps.tbrActive) { + extendedJson.put("TempBasalAbsoluteRate", ps.basalRate); + extendedJson.put("TempBasalPercent", ps.tbrPercent); + extendedJson.put("TempBasalRemaining", ps.tbrRemainingDuration); + } + if (ps.activeAlert != null && ps.activeAlert.errorCode != null) { + extendedJson.put("ErrorCode", ps.activeAlert.errorCode); + } + pumpJson.put("extended", extendedJson); + + JSONObject batteryJson = new JSONObject(); + int battery = 100; + if (ps.batteryState == PumpState.LOW) battery = 25; + else if (ps.batteryState == PumpState.EMPTY) battery = 0; + batteryJson.put("percent", battery); + pumpJson.put("battery", batteryJson); + + return pumpJson; + } catch (Exception e) { + log.warn("Failed to gather device status for upload", e); + } + + return null; + } + + @Override + public String deviceID() { + return "Combo"; + } + + @Override + public PumpDescription getPumpDescription() { + return pumpDescription; + } + + @Override + public String shortStatus(boolean veryShort) { + return getStateSummary(); + } + + @Override + public boolean isFakingTempsByExtendedBoluses() { + return false; + } + + @Override + public PumpEnactResult loadTDDs() { + PumpEnactResult result = new PumpEnactResult(); + result.success = readHistory(new PumpHistoryRequest().tddHistory(PumpHistoryRequest.FULL)); + if (result.success) { + List tdds = pump.tddHistory; + if (tdds != null) { + HashMap map = new HashMap<>(); + for (int i = 0; i < tdds.size(); i++) { + Tdd currTdd = tdds.get(i); + if (currTdd.total < 1) + continue; //cases where dummy days are introduced (e.g. Battery change with date loss) + if (map.containsKey(currTdd.timestamp)) { + //duplicate days on time changes + TDD existing = map.get(currTdd.timestamp); + existing.total += currTdd.total; + } else { + map.put(currTdd.timestamp, new TDD(currTdd.timestamp, 0d, 0d, currTdd.total)); + } + } + + Collection uniqueColl = map.values(); + + for (TDD currTdd : uniqueColl) { + MainApp.getDbHelper().createOrUpdateTDD(currTdd); + } + } + } + return result; + } + + // Constraints interface + private long lowSuspendOnlyLoopEnforcedUntil = 0; + private long violationWarningRaisedForBolusAt = 0; + private boolean validBasalRateProfileSelectedOnPump = true; + + @Override + public Constraint isLoopInvokationAllowed(Constraint value) { + if (!validBasalRateProfileSelectedOnPump) + value.set(false, MainApp.gs(R.string.novalidbasalrate), this); + return value; + } + + @Override + public Constraint applyMaxIOBConstraints(Constraint maxIob) { + if (lowSuspendOnlyLoopEnforcedUntil > System.currentTimeMillis()) + maxIob.setIfSmaller(0d, String.format(MainApp.gs(R.string.limitingmaxiob), 0d, MainApp.gs(R.string.unsafeusage)), this); + return maxIob; + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPump.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPump.java new file mode 100644 index 0000000000..aef0f2abd5 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPump.java @@ -0,0 +1,31 @@ +package info.nightscout.androidaps.plugins.PumpCombo; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; + +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BasalProfile; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpState; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Bolus; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpAlert; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Tdd; + +class ComboPump { + boolean initialized = false; + volatile long lastSuccessfulCmdTime; + + public volatile String activity; + @NonNull + volatile PumpState state = new PumpState(); + volatile int reservoirLevel = -1; + @NonNull + volatile BasalProfile basalProfile = new BasalProfile(); + @Nullable + volatile Bolus lastBolus; + + // Alert and TDD histories are not stored in DB, but are read on demand and just cached here + List errorHistory = new ArrayList<>(0); + List tddHistory = new ArrayList<>(0); +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/events/EventComboPumpUpdateGUI.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/events/EventComboPumpUpdateGUI.java new file mode 100644 index 0000000000..e9bf3f8415 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/events/EventComboPumpUpdateGUI.java @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.plugins.PumpCombo.events; + +/** + * Created by mike on 24.05.2017. + */ + +public class EventComboPumpUpdateGUI { +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/BasalProfile.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/BasalProfile.java new file mode 100644 index 0000000000..fa9c2dceb0 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/BasalProfile.java @@ -0,0 +1,42 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter; + +import java.util.Arrays; + +public class BasalProfile { + public final double[] hourlyRates; + + public BasalProfile() { + this.hourlyRates = new double[24]; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BasalProfile that = (BasalProfile) o; + + for(int i = 0; i < 24; i++) { + if (Math.abs(hourlyRates[i] - that.hourlyRates[i]) > 0.001) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + return Arrays.hashCode(hourlyRates); + } + + @Override + public String toString() { + double total = 0d; + for(int i = 0; i < 24; i++) { + total += hourlyRates[i]; + } + return "BasalProfile{" + + "hourlyRates=" + Arrays.toString(hourlyRates) + ", total " + total + " U" + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/BolusProgressReporter.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/BolusProgressReporter.java new file mode 100644 index 0000000000..59d334fe6f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/BolusProgressReporter.java @@ -0,0 +1,14 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter; + +public interface BolusProgressReporter { + enum State { + PROGRAMMING, + DELIVERING, + DELIVERED, + STOPPING, + STOPPED, + RECOVERING + } + + void report(State state, int percent, double delivered); +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/CommandResult.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/CommandResult.java new file mode 100644 index 0000000000..9da5d4d6fa --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/CommandResult.java @@ -0,0 +1,58 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter; + +import android.support.annotation.Nullable; + +import java.util.LinkedList; +import java.util.List; + +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Bolus; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpHistory; + +public class CommandResult { + /** Whether the command was executed successfully. */ + public boolean success; + /** State of the pump *after* command execution. */ + public PumpState state; + /** History if requested by the command. */ + @Nullable + public PumpHistory history; + /** Basal rate profile if requested. */ + public BasalProfile basalProfile; + + /** Warnings raised on the pump that are forwarded to AAPS to be turned into AAPS + * notifications. */ + public List forwardedWarnings = new LinkedList<>(); + + public int reservoirLevel = -1; + + public CommandResult success(boolean success) { + this.success = success; + return this; + } + + public CommandResult state(PumpState state) { + this.state = state; + return this; + } + + public CommandResult history(PumpHistory history) { + this.history = history; + return this; + } + + public CommandResult basalProfile(BasalProfile basalProfile) { + this.basalProfile = basalProfile; + return this; + } + + @Override + public String toString() { + return "CommandResult{" + + "success=" + success + + ", state=" + state + + ", history=" + history + + ", basalProfile=" + basalProfile + + ", forwardedWarnings='" + forwardedWarnings + '\'' + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/PumpErrorCodes.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/PumpErrorCodes.java new file mode 100644 index 0000000000..63cd81e3a6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/PumpErrorCodes.java @@ -0,0 +1,17 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter; + +public class PumpErrorCodes { + public static final int CARTRIDGE_EMPTY = 1; + public static final int BATTERY_EMPTY = 2; + public static final int AUTOMATIC_OFF = 3; + public static final int OCCLUSION = 4; + public static final int END_OF_OPERATION_BACKUP_PUMP = 5; + public static final int MECHANICAL_ERROR = 6; + public static final int ELECTRONIC_ERROR = 7; + public static final int POWER_INTERRUPT = 8; + public static final int END_OF_OPERATION_LOAN_PUMP = 9; + public static final int CARTRIDGE_ERROR = 10; + public static final int SET_NOT_PRIMED = 11; + public static final int DATA_INTERRUPTED = 12; + public static final int LANGUAGE_ERROR = 13; +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/PumpState.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/PumpState.java new file mode 100644 index 0000000000..963cdd8b4c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/PumpState.java @@ -0,0 +1,103 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter; + +/** State displayed on the main screen of the pump. */ +public class PumpState { + /** Time the state was captured. */ + public long timestamp; + /** Pump time. Note that this is derived from the time displayed on the main menu and assumes + * the date is set correctly */ + public long pumpTime; + public String menu = null; + public boolean suspended; + + public boolean tbrActive = false; + /** TBR percentage. 100% means no TBR active, just the normal basal rate running. */ + public int tbrPercent = -1; + /** The absolute rate the pump is running (regular basal rate or TBR), e.g. 0.80U/h. */ + public double basalRate = -1; + /** Remaining time of an active TBR. Note that 0:01 is te lowest displayed, the pump + * jumps from that to TBR end, skipping 0:00(xx). */ + public int tbrRemainingDuration = -1; + + /** Warning or error code displayed if a warning or alert alert is active, + * see {@link PumpWarningCodes}, {@link PumpErrorCodes} */ + public WarningOrErrorCode activeAlert; + + public static final int UNKNOWN = -1; + public static final int LOW = 1; + public static final int EMPTY = 2; + public int batteryState = UNKNOWN; + public int insulinState = UNKNOWN; + + public int activeBasalProfileNumber; + + public static final int SAFE_USAGE = 0; + public static final int UNSUPPORTED_BOLUS_TYPE = 1; + public static final int UNSUPPORTED_BASAL_RATE_PROFILE = 2; + /** True if use of an extended or multiwave bolus has been detected */ + public int unsafeUsageDetected = SAFE_USAGE; + + public PumpState menu(String menu) { + this.menu = menu; + return this; + } + + public PumpState tbrActive(boolean tbrActive) { + this.tbrActive = tbrActive; + return this; + } + + public PumpState tbrPercent(int tbrPercent) { + this.tbrPercent = tbrPercent; + return this; + } + + public PumpState basalRate(double basalRate) { + this.basalRate = basalRate; + return this; + } + + public PumpState tbrRemainingDuration(int tbrRemainingDuration) { + this.tbrRemainingDuration = tbrRemainingDuration; + return this; + } + + public PumpState suspended(boolean suspended) { + this.suspended = suspended; + return this; + } + + public PumpState batteryState(int batteryState) { + this.batteryState = batteryState; + return this; + } + + public PumpState insulinState(int insulinState) { + this.insulinState = insulinState; + return this; + } + + public PumpState activeBasalProfileNumber(int activeBasalProfileNumber) { + this.activeBasalProfileNumber = activeBasalProfileNumber; + return this; + } + + @Override + public String toString() { + return "PumpState{" + + "timestamp=" + timestamp + + ", pumpTime=" + pumpTime + + ", menu='" + menu + '\'' + + ", suspended=" + suspended + + ", tbrActive=" + tbrActive + + ", tbrPercent=" + tbrPercent + + ", basalRate=" + basalRate + + ", tbrRemainingDuration=" + tbrRemainingDuration + + ", activeAlert=" + activeAlert + + ", batteryState=" + batteryState + + ", insulinState=" + insulinState + + ", activeBasalProfileNumber=" + activeBasalProfileNumber + + ", unsafeUsageDetected=" + unsafeUsageDetected + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/PumpWarningCodes.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/PumpWarningCodes.java new file mode 100644 index 0000000000..60d1898834 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/PumpWarningCodes.java @@ -0,0 +1,14 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter; + +public class PumpWarningCodes { + public static final int CARTRIDGE_LOW = 1; + public static final int BATTERY_LOW = 2; + public static final int REVIEW_TIME = 3; + public static final int CALL_FOR_UPDATE = 4; + public static final int PUMP_TIMER = 5; + public static final int TBR_CANCELLED = 6; + public static final int TBR_OVER = 7; + public static final int BOLUS_CANCELLED = 8; + public static final int LOANTIME_WARNING = 9; + public static final int BLUETOOTH_FAULT = 10; +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/RuffyCommands.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/RuffyCommands.java new file mode 100644 index 0000000000..5918023d1e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/RuffyCommands.java @@ -0,0 +1,49 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter; + +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpHistoryRequest; + +public interface RuffyCommands { + /** Issues a bolus issues updates on progress through via {@link BolusProgressReporter}. */ + CommandResult deliverBolus(double amount, BolusProgressReporter bolusProgressReporter); + + /** Requests cancellation of an active bolus if possible. */ + void cancelBolus(); + + CommandResult setTbr(int percent, int duration); + + CommandResult cancelTbr(); + + /** Confirms an active warning alert on the pump. + * @see PumpWarningCodes */ + CommandResult confirmAlert(int warningCode); + + /** Indicate if the pump is ready to receive commands. */ + boolean isPumpAvailable(); + + /** Indicate of the pump is busy processing a command. */ + boolean isPumpBusy(); + + /** Whether there's usable connection to the pump. */ + boolean isConnected(); + + void disconnect(); + + /** Read the state of the pump, which encompasses all information displayed on the main menu. */ + CommandResult readPumpState(); + + /** Read reservoir level and last bolus via Quick Info */ + CommandResult readQuickInfo(int numberOfBolusRecordsToRetrieve); + + /** Reads pump history via the My Data menu. The {@link PumpHistoryRequest} specifies + * what types of data and how far back data is returned. */ + CommandResult readHistory(PumpHistoryRequest request); + + CommandResult readBasalProfile(); + + CommandResult setBasalProfile(BasalProfile basalProfile); + + CommandResult getDateAndTime(); + + CommandResult setDateAndTime(); +} + diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/RuffyScripter.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/RuffyScripter.java new file mode 100644 index 0000000000..1e7e542e5f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/RuffyScripter.java @@ -0,0 +1,942 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.crashlytics.android.answers.CustomEvent; +import com.google.common.base.Joiner; + +import org.monkey.d.ruffy.ruffy.driver.IRTHandler; +import org.monkey.d.ruffy.ruffy.driver.IRuffyService; +import org.monkey.d.ruffy.ruffy.driver.display.Menu; +import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute; +import org.monkey.d.ruffy.ruffy.driver.display.MenuType; +import org.monkey.d.ruffy.ruffy.driver.display.menu.BolusType; +import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; +import java.util.List; +import java.util.Objects; + +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.ReadQuickInfoCommand; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpHistoryRequest; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.BolusCommand; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.CancelTbrCommand; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.Command; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.CommandException; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.ConfirmAlertCommand; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.ReadBasalProfileCommand; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.ReadHistoryCommand; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.ReadPumpStateCommand; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.SetBasalProfileCommand; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands.SetTbrCommand; +import info.nightscout.androidaps.BuildConfig; +import info.nightscout.utils.FabricPrivacy; + +/** + * Provides scripting 'runtime' and operations. consider moving operations into a separate + * class and inject that into executing commands, so that commands operately solely on + * operations and are cleanly separated from the thread management, connection management etc + */ +public class RuffyScripter implements RuffyCommands { + private static final Logger log = LoggerFactory.getLogger(RuffyScripter.class); + + private IRuffyService ruffyService; + + @Nullable + private volatile Menu currentMenu; + private volatile long menuLastUpdated = 0; + private volatile boolean unparsableMenuEncountered; + + + private String previousCommand = ""; + private volatile Command activeCmd = null; + + private boolean started = false; + + private final Object screenlock = new Object(); + + private IRTHandler mHandler = new IRTHandler.Stub() { + @Override + public void log(String message) throws RemoteException { + if (log.isTraceEnabled()) { + log.trace("Ruffy says: " + message); + } + } + + @Override + public void fail(String message) throws RemoteException { + log.warn("Ruffy warns: " + message); + if (message.startsWith("no connection possible")) + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboRuffyWarning").putCustomAttribute("message", "no connection possible")); + else if (message.startsWith("Error sending keep alive while rtModeRunning is still true")) + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboRuffyWarning").putCustomAttribute("message", "Error sending keep alive while rtModeRunning is still true")); + else if (message.startsWith("Error sending keep alive. rtModeRunning is false, so this is most likely a race condition during disconnect")) + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboRuffyWarning").putCustomAttribute("message", "Error sending keep alive. rtModeRunning is false, so this is most likely a race condition during disconnect")); + else + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboRuffyWarning").putCustomAttribute("message", message.substring(0, 98))); + } + + @Override + public void requestBluetooth() throws RemoteException { + log.trace("Ruffy invoked requestBluetooth callback"); + } + + @Override + public void rtStopped() throws RemoteException { + log.debug("rtStopped callback invoked"); + currentMenu = null; + } + + @Override + public void rtStarted() throws RemoteException { + log.debug("rtStarted callback invoked"); + } + + @Override + public void rtClearDisplay() throws RemoteException { + } + + @Override + public void rtUpdateDisplay(byte[] quarter, int which) throws RemoteException { + } + + @Override + public void rtDisplayHandleMenu(Menu menu) throws RemoteException { + // method is called every ~500ms + log.debug("rtDisplayHandleMenu: " + menu); + + currentMenu = menu; + menuLastUpdated = System.currentTimeMillis(); + + synchronized (screenlock) { + screenlock.notifyAll(); + } + } + + @Override + public void rtDisplayHandleNoMenu() throws RemoteException { + log.warn("rtDisplayHandleNoMenu callback invoked"); + unparsableMenuEncountered = true; + } + }; + + public RuffyScripter(Context context) { + boolean boundSucceeded = false; + + try { + Intent intent = new Intent() + .setComponent(new ComponentName( + // this must be the base package of the app (check package attribute in + // manifest element in the manifest file of the providing app) + "org.monkey.d.ruffy.ruffy", + // full path to the driver; + // in the logs this service is mentioned as (note the slash) + // "org.monkey.d.ruffy.ruffy/.driver.Ruffy"; + // org.monkey.d.ruffy.ruffy is the base package identifier + // and /.driver.Ruffy the service within the package + "org.monkey.d.ruffy.ruffy.driver.Ruffy" + )); + context.startService(intent); + + ServiceConnection mRuffyServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + log.debug("ruffy service connected"); + ruffyService = IRuffyService.Stub.asInterface(service); + try { + ruffyService.setHandler(mHandler); + } catch (Exception e) { + log.error("Ruffy handler has issues", e); + } + started = true; + } + + @Override + public void onServiceDisconnected(ComponentName name) { + log.debug("ruffy service disconnected"); + } + }; + boundSucceeded = context.bindService(intent, mRuffyServiceConnection, Context.BIND_AUTO_CREATE); + } catch (Exception e) { + log.error("Binding to ruffy service failed", e); + } + + if (!boundSucceeded) { + log.error("No connection to ruffy. Pump control unavailable."); + } else { + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboScripterInit") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION)); + } + } + + @Override + public boolean isPumpAvailable() { + return started; + } + + @Override + public boolean isPumpBusy() { + return activeCmd != null; + } + + @Override + public boolean isConnected() { + if (ruffyService == null) { + return false; + } + try { + if (!ruffyService.isConnected()) { + return false; + } + return ruffyService.isConnected() && System.currentTimeMillis() - menuLastUpdated < 10 * 1000; + } catch (RemoteException e) { + return false; + } + } + + @Override + public synchronized void disconnect() { + if (ruffyService == null) { + return; + } + try { + log.debug("Disconnecting"); + ruffyService.doRTDisconnect(); + } catch (RemoteException e) { + // ignore + } catch (Exception e) { + log.warn("Disconnect not happy", e); + } + } + + @Override + public CommandResult readPumpState() { + return runCommand(new ReadPumpStateCommand()); + } + + @Override + public CommandResult readQuickInfo(int numberOfBolusRecordsToRetrieve) { + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboReadQuickInfoCmd") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION)); + return runCommand(new ReadQuickInfoCommand(numberOfBolusRecordsToRetrieve)); + } + + public void returnToRootMenu() { + // returning to main menu using the 'back' key does not cause a vibration + MenuType menuType = getCurrentMenu().getType(); + while (menuType != MenuType.MAIN_MENU && menuType != MenuType.STOP && menuType != MenuType.WARNING_OR_ERROR) { + log.debug("Going back to main menu, currently at " + menuType); + pressBackKey(); + while (getCurrentMenu().getType() == menuType) { + waitForScreenUpdate(); + } + menuType = getCurrentMenu().getType(); + } + } + + /** + * Always returns a CommandResult, never throws + */ + private CommandResult runCommand(final Command cmd) { + log.debug("Attempting to run cmd: " + cmd); + + List violations = cmd.validateArguments(); + if (!violations.isEmpty()) { + log.error("Command argument violations: " + Joiner.on(", ").join(violations)); + return new CommandResult().success(false).state(new PumpState()); + } + + synchronized (RuffyScripter.class) { + Thread cmdThread = null; + try { + activeCmd = cmd; + long connectStart = System.currentTimeMillis(); + ensureConnected(); + log.debug("Connection ready to execute cmd " + cmd); + cmdThread = new Thread(() -> { + try { + if (!runPreCommandChecks(cmd)) { + return; + } + PumpState pumpState = readPumpStateInternal(); + log.debug("Pump state before running command: " + pumpState); + + // execute the command + cmd.setScripter(RuffyScripter.this); + long cmdStartTime = System.currentTimeMillis(); + cmd.execute(); + long cmdEndTime = System.currentTimeMillis(); + log.debug("Executing " + cmd + " took " + (cmdEndTime - cmdStartTime) + "ms"); + } catch (CommandException e) { + log.error("CommandException running command", e); + activeCmd.getResult().success = false; + } catch (Exception e) { + log.error("Unexpected exception running cmd", e); + activeCmd.getResult().success = false; + } + }, cmd.getClass().getSimpleName()); + long executionStart = System.currentTimeMillis(); + cmdThread.start(); + + long overallTimeout = System.currentTimeMillis() + 10 * 60 * 1000; + while (cmdThread.isAlive()) { + if (!isConnected()) { + // on connection loss try to reconnect, confirm warning alerts caused by + // the disconnected and then return the command as failed (the caller + // can retry if needed). + log.debug("Connection unusable (ruffy connection: " + ruffyService.isConnected() + ", " + + "time since last menu update: " + (System.currentTimeMillis() - menuLastUpdated) + " ms, " + + "aborting command and attempting reconnect ..."); + cmdThread.interrupt(); + activeCmd.getResult().success = false; + + // the BT connection might be still there, but we might not be receiving + // menu updates, so force a disconnect before connecting again + disconnect(); + SystemClock.sleep(500); + for (int attempts = 2; attempts > 0; attempts--) { + boolean reconnected = recoverFromConnectionLoss(); + if (reconnected) { + break; + } + // connect attempt times out after 90s, shortly wait and then retry; + // (90s timeout + 5s wait) * 2 attempts = 190s + SystemClock.sleep(5 * 1000); + } + break; + } + + if (System.currentTimeMillis() > overallTimeout) { + log.error("Command " + cmd + " timed out"); + cmdThread.interrupt(); + activeCmd.getResult().success = false; + break; + } + + if (unparsableMenuEncountered) { + log.error("UnparsableMenuEncountered flagged, aborting command"); + cmdThread.interrupt(); + activeCmd.getResult().success = false; + } + + log.trace("Waiting for running command to complete"); + SystemClock.sleep(500); + } + + activeCmd.getResult().state = readPumpStateInternal(); + CommandResult result = activeCmd.getResult(); + if (log.isDebugEnabled()) { + long connectDurationSec = (executionStart - connectStart) / 1000; + long executionDurationSec = (System.currentTimeMillis() - executionStart) / 1000; + log.debug("Command result: " + result); + log.debug("Connect: " + connectDurationSec + "s, execution: " + executionDurationSec + "s"); + } + return result; + } catch (CommandException e) { + log.error("CommandException while executing command", e); + PumpState pumpState = recoverFromCommandFailure(); + return activeCmd.getResult().success(false).state(pumpState); + } catch (Exception e) { + log.error("Unexpected exception communication with ruffy", e); + PumpState pumpState = recoverFromCommandFailure(); + return activeCmd.getResult().success(false).state(pumpState); + } finally { + Menu menu = this.currentMenu; + if (activeCmd.getResult().success && menu != null && menu.getType() != MenuType.MAIN_MENU) { + log.warn("Command " + activeCmd + " successful, but finished leaving pump on menu " + getCurrentMenuName()); + } + if (cmdThread != null) { + try { + // let command thread finish updating activeCmd var + cmdThread.join(1000); + } catch (InterruptedException e) { + // ignore + } + } + previousCommand = "" + activeCmd; + activeCmd = null; + } + } + } + + private boolean runPreCommandChecks(Command cmd) { + if (cmd instanceof ReadPumpStateCommand) { + // always allowed, state is set at the end of runCommand method + activeCmd.getResult().success = true; + } else if (getCurrentMenu().getType() == MenuType.STOP) { + if (cmd.needsRunMode()) { + log.error("Requested command requires run mode, but pump is suspended"); + activeCmd.getResult().success = false; + return false; + } + } else if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) { + if (!(cmd instanceof ConfirmAlertCommand)) { + log.warn("Warning/alert active on pump, but requested command is not ConfirmAlertCommand"); + activeCmd.getResult().success = false; + return false; + } + } else if (getCurrentMenu().getType() != MenuType.MAIN_MENU) { + log.debug("Pump is unexpectedly not on main menu but " + getCurrentMenuName() + ", trying to recover"); + try { + recoverFromCommandFailure(); + } catch (Exception e) { + activeCmd.getResult().success = false; + return false; + } + if (getCurrentMenu().getType() != MenuType.MAIN_MENU) { + activeCmd.getResult().success = false; + return false; + } + } + return true; + } + + /** + * On connection loss the pump raises an alert immediately (when setting a TBR or giving a bolus) - + * there's no timeout before that happens. But: a reconnect is still possible which can then + * confirm the alert. + * + * @return whether the reconnect and return to main menu was successful + */ + private boolean recoverFromConnectionLoss() { + log.debug("Connection was lost, trying to reconnect"); + ensureConnected(); + if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) { + WarningOrErrorCode warningOrErrorCode = readWarningOrErrorCode(); + if (Objects.equals(activeCmd.getReconnectWarningId(), warningOrErrorCode.warningCode)) { + log.debug("Confirming warning caused by disconnect: #" + warningOrErrorCode.warningCode); + // confirm alert + verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR); + pressCheckKey(); + // dismiss alert + verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR); + pressCheckKey(); + } + } + + boolean connected = isConnected(); + if (connected) { + MenuType menuType = getCurrentMenu().getType(); + if (menuType != MenuType.MAIN_MENU && menuType != MenuType.WARNING_OR_ERROR) { + returnToRootMenu(); + } + } + log.debug("Recovery from connection loss " + (connected ? "succeeded" : "failed")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboRecoveryFromConnectionLoss") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION) + .putCustomAttribute("activeCommand", "" + (activeCmd != null ? activeCmd.getClass().getSimpleName() : "")) + .putCustomAttribute("success", connected ? "true" : "else")); + return connected; + } + + /** + * Returns to the main menu (if possible) after a command failure, so that subsequent commands + * reusing the connection won't fail and returns the current PumpState (empty if unreadable). + */ + private PumpState recoverFromCommandFailure() { + Menu menu = this.currentMenu; + if (menu == null) { + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboRecoveryFromCommandFailure") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION) + .putCustomAttribute("activeCommand", "" + (activeCmd != null ? activeCmd.getClass().getSimpleName() : "")) + .putCustomAttribute("exit", "1") + .putCustomAttribute("success", "false")); + return new PumpState(); + } + MenuType type = menu.getType(); + if (type != MenuType.WARNING_OR_ERROR && type != MenuType.MAIN_MENU) { + try { + log.debug("Command execution yielded an error, returning to main menu"); + returnToRootMenu(); + } catch (Exception e) { + log.warn("Error returning to main menu, when trying to recover from command failure", e); + } + } + try { + PumpState pumpState = readPumpStateInternal(); + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboRecoveryFromCommandFailure") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION) + .putCustomAttribute("activeCommand", "" + (activeCmd != null ? activeCmd.getClass().getSimpleName() : "")) + .putCustomAttribute("exit", "2") + .putCustomAttribute("success", "true")); + return pumpState; + } catch (Exception e) { + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboRecoveryFromCommandFailure") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION) + .putCustomAttribute("exit", "3") + .putCustomAttribute("activeCommand", "" + (activeCmd != null ? activeCmd.getClass().getSimpleName() : "")) + .putCustomAttribute("success", "false")); + + log.debug("Reading pump state during recovery failed", e); + return new PumpState(); + } + } + + /** + * If there's an issue, this times out eventually and throws a CommandException + */ + private void ensureConnected() { + try { + if (isConnected()) { + return; + } + + boolean connectInitSuccessful = ruffyService.doRTConnect() == 0; + log.debug("Connect init successful: " + connectInitSuccessful); + log.debug("Waiting for first menu update to be sent"); + long timeoutExpired = System.currentTimeMillis() + 90 * 1000; + long initialUpdateTime = menuLastUpdated; + while (initialUpdateTime == menuLastUpdated) { + if (System.currentTimeMillis() > timeoutExpired) { + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboConnectTimeout") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION) + .putCustomAttribute("activeCommand", "" + (activeCmd != null ? activeCmd.getClass().getSimpleName() : "")) + .putCustomAttribute("previousCommand", previousCommand)); + throw new CommandException("Timeout connecting to pump"); + } + SystemClock.sleep(50); + } + } catch (CommandException e) { + try { + ruffyService.doRTDisconnect(); + } catch (RemoteException e1) { + log.warn("Disconnect after connect failure failed", e1); + } + throw e; + } catch (Exception e) { + try { + ruffyService.doRTDisconnect(); + } catch (RemoteException e1) { + log.warn("Disconnect after connect failure failed", e1); + } + throw new CommandException("Unexpected exception while initiating/restoring pump connection", e); + } + } + + /** + * This reads the state of the pump, which is whatever is currently displayed on the display, + * no actions are performed. + */ + public PumpState readPumpStateInternal() { + PumpState state = new PumpState(); + state.timestamp = System.currentTimeMillis(); + Menu menu = currentMenu; + if (menu == null) { + log.debug("Returning empty PumpState, menu is unavailable"); + return state; + } + + log.debug("Parsing menu: " + menu); + MenuType menuType = menu.getType(); + state.menu = menuType.name(); + + if (menuType == MenuType.MAIN_MENU) { + Double tbrPercentage = (Double) menu.getAttribute(MenuAttribute.TBR); + BolusType bolusType = (BolusType) menu.getAttribute(MenuAttribute.BOLUS_TYPE); + Integer activeBasalRate = (Integer) menu.getAttribute(MenuAttribute.BASAL_SELECTED); + + if (!activeBasalRate.equals(1)) { + state.unsafeUsageDetected = PumpState.UNSUPPORTED_BASAL_RATE_PROFILE; + } else if (bolusType != null && bolusType != BolusType.NORMAL) { + state.unsafeUsageDetected = PumpState.UNSUPPORTED_BOLUS_TYPE; + } else if (tbrPercentage != null && tbrPercentage != 100) { + state.tbrActive = true; + Double displayedTbr = (Double) menu.getAttribute(MenuAttribute.TBR); + state.tbrPercent = displayedTbr.intValue(); + MenuTime durationMenuTime = ((MenuTime) menu.getAttribute(MenuAttribute.RUNTIME)); + state.tbrRemainingDuration = durationMenuTime.getHour() * 60 + durationMenuTime.getMinute(); + } + if (menu.attributes().contains(MenuAttribute.BASAL_RATE)) { + state.basalRate = ((double) menu.getAttribute(MenuAttribute.BASAL_RATE)); + } + if (menu.attributes().contains(MenuAttribute.BATTERY_STATE)) { + state.batteryState = ((int) menu.getAttribute(MenuAttribute.BATTERY_STATE)); + } + if (menu.attributes().contains(MenuAttribute.INSULIN_STATE)) { + state.insulinState = ((int) menu.getAttribute(MenuAttribute.INSULIN_STATE)); + } + if (menu.attributes().contains(MenuAttribute.TIME)) { + MenuTime pumpTime = (MenuTime) menu.getAttribute(MenuAttribute.TIME); + Date date = new Date(); + // infer yesterday as the pump's date if midnight just passed, but the pump is + // a bit behind + if (date.getHours() == 0 && date.getMinutes() <= 5 + && pumpTime.getHour() == 23 && pumpTime.getMinute() >= 55) { + date.setTime(date.getTime() - 24 * 60 * 60 * 1000); + } + date.setHours(pumpTime.getHour()); + date.setMinutes(pumpTime.getMinute()); + date.setSeconds(0); + state.pumpTime = date.getTime() - date.getTime() % 1000; + } + } else if (menuType == MenuType.WARNING_OR_ERROR) { + state.activeAlert = readWarningOrErrorCode(); + } else if (menuType == MenuType.STOP) { + state.suspended = true; + if (menu.attributes().contains(MenuAttribute.BATTERY_STATE)) { + state.batteryState = ((int) menu.getAttribute(MenuAttribute.BATTERY_STATE)); + } + if (menu.attributes().contains(MenuAttribute.INSULIN_STATE)) { + state.insulinState = ((int) menu.getAttribute(MenuAttribute.INSULIN_STATE)); + } + if (menu.attributes().contains(MenuAttribute.TIME)) { + MenuTime time = (MenuTime) menu.getAttribute(MenuAttribute.TIME); + Date date = new Date(); + date.setHours(time.getHour()); + date.setMinutes(time.getMinute()); + date.setSeconds(0); + state.pumpTime = date.getTime() - date.getTime() % 1000; + } + } + + log.debug("State read: " + state); + return state; + } + + @NonNull + public WarningOrErrorCode readWarningOrErrorCode() { + if (currentMenu == null || getCurrentMenu().getType() != MenuType.WARNING_OR_ERROR) { + return new WarningOrErrorCode(null, null, null); + } + Integer warningCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.WARNING); + Integer errorCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.ERROR); + int retries = 5; + while (warningCode == null && errorCode == null && retries > 0) { + waitForScreenUpdate(); + warningCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.WARNING); + errorCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.ERROR); + retries--; + } + String message = (String) getCurrentMenu().getAttribute(MenuAttribute.MESSAGE); + return new WarningOrErrorCode(warningCode, errorCode, message); + } + + public static class Key { + public static byte NO_KEY = (byte) 0x00; + public static byte MENU = (byte) 0x03; + public static byte CHECK = (byte) 0x0C; + public static byte UP = (byte) 0x30; + public static byte DOWN = (byte) 0xC0; + public static byte BACK = (byte) 0x33; + } + + // === pump ops === + @NonNull + public Menu getCurrentMenu() { + if (Thread.currentThread().isInterrupted()) + throw new CommandException("Interrupted"); + Menu menu = this.currentMenu; + if (menu == null) { + log.error("currentMenu == null, bailing"); + throw new CommandException("Unable to read current menu"); + } + return menu; + } + + @Nullable + private String getCurrentMenuName() { + Menu menu = this.currentMenu; + return menu != null ? menu.getType().toString() : ""; + } + + public void pressUpKey() { + log.debug("Pressing up key"); + pressKey(Key.UP); + log.debug("Releasing up key"); + } + + public void pressDownKey() { + log.debug("Pressing down key"); + pressKey(Key.DOWN); + log.debug("Releasing down key"); + } + + public void pressCheckKey() { + log.debug("Pressing check key"); + pressKey(Key.CHECK); + log.debug("Releasing check key"); + } + + public void pressMenuKey() { + log.debug("Pressing menu key"); + pressKey(Key.MENU); + log.debug("Releasing menu key"); + } + + private void pressBackKey() { + log.debug("Pressing back key"); + pressKey(Key.BACK); + log.debug("Releasing back key"); + } + + public void pressKeyMs(final byte key, long ms) { + long stepMs = 100; + try { + log.debug("Scroll: Pressing key for " + ms + " ms with step " + stepMs + " ms"); + ruffyService.rtSendKey(key, true); + ruffyService.rtSendKey(key, false); + while (ms > stepMs) { + SystemClock.sleep(stepMs); + ruffyService.rtSendKey(key, false); + ms -= stepMs; + } + SystemClock.sleep(ms); + ruffyService.rtSendKey(Key.NO_KEY, true); + log.debug("Releasing key"); + } catch (Exception e) { + throw new CommandException("Error while pressing buttons"); + } + } + + /** + * Wait until the menu is updated + */ + public void waitForScreenUpdate() { + if (Thread.currentThread().isInterrupted()) + throw new CommandException("Interrupted"); + synchronized (screenlock) { + try { + // updates usually come in every ~500, occasionally up to 1100ms + screenlock.wait((long) 2000); + } catch (InterruptedException e) { + throw new CommandException("Interrupted"); + } + } + } + + private void pressKey(final byte key) { + if (Thread.currentThread().isInterrupted()) + throw new CommandException("Interrupted"); + try { + ruffyService.rtSendKey(key, true); + SystemClock.sleep(150); + ruffyService.rtSendKey(Key.NO_KEY, true); + } catch (Exception e) { + throw new CommandException("Error while pressing buttons"); + } + } + + public void navigateToMenu(MenuType desiredMenu) { + verifyMenuIsDisplayed(MenuType.MAIN_MENU); + int moves = 20; + MenuType lastSeenMenu = getCurrentMenu().getType(); + while (lastSeenMenu != desiredMenu) { + log.debug("Navigating to menu " + desiredMenu + ", current menu: " + lastSeenMenu); + moves--; + if (moves == 0) { + throw new CommandException("Menu not found searching for " + desiredMenu + + ". Check menu settings on your pump to ensure it's not hidden."); + } + MenuType next = getCurrentMenu().getType(); + pressMenuKey(); + // sometimes the pump takes a bit longer (more than one screen refresh) to advance + // to the next menu. wait until we actually see the change to avoid overshoots. + while (next == lastSeenMenu) { + waitForScreenUpdate(); + next = getCurrentMenu().getType(); + } + lastSeenMenu = getCurrentMenu().getType(); + } + } + + /** + * Wait till a menu changed has completed, "away" from the menu provided as argument. + */ + public void waitForMenuToBeLeft(MenuType menuType) { + long timeout = System.currentTimeMillis() + 10 * 1000; + while (getCurrentMenu().getType() == menuType) { + if (System.currentTimeMillis() > timeout) { + throw new CommandException("Timeout waiting for menu " + menuType + " to be left"); + } + waitForScreenUpdate(); + } + } + + public void verifyMenuIsDisplayed(MenuType expectedMenu) { + verifyMenuIsDisplayed(expectedMenu, null); + } + + public void verifyMenuIsDisplayed(MenuType expectedMenu, String failureMessage) { + int attempts = 5; + while (getCurrentMenu().getType() != expectedMenu) { + attempts -= 1; + if (attempts > 0) { + waitForScreenUpdate(); + } else { + if (failureMessage == null) { + failureMessage = "Invalid pump state, expected to be in menu " + expectedMenu + ", but current menu is " + getCurrentMenuName(); + } + throw new CommandException(failureMessage); + } + } + } + + public void verifyRootMenuIsDisplayed() { + int retries = 600; + while (getCurrentMenu().getType() != MenuType.MAIN_MENU && getCurrentMenu().getType() != MenuType.STOP) { + if (retries > 0) { + SystemClock.sleep(100); + retries = retries - 1; + } else { + throw new CommandException("Invalid pump state, expected to be in menu MAIN or STOP but current menu is " + getCurrentMenuName()); + } + } + } + + @SuppressWarnings("unchecked") + public T readBlinkingValue(Class expectedType, MenuAttribute attribute) { + int retries = 5; + Object value = getCurrentMenu().getAttribute(attribute); + while (!expectedType.isInstance(value)) { + value = getCurrentMenu().getAttribute(attribute); + waitForScreenUpdate(); + retries--; + if (retries == 0) { + throw new CommandException("Failed to read blinkng value: " + attribute + "=" + value + " type=" + value); + } + } + return (T) value; + } + + @Override + public CommandResult deliverBolus(double amount, BolusProgressReporter bolusProgressReporter) { + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboBolusCmd") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION)); + return runCommand(new BolusCommand(amount, bolusProgressReporter)); + } + + @Override + public void cancelBolus() { + if (activeCmd instanceof BolusCommand) { + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboBolusCmdCancel") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION)); + ((BolusCommand) activeCmd).requestCancellation(); + } else { + log.error("cancelBolus called, but active command is not a bolus:" + activeCmd); + } + } + + @Override + public CommandResult setTbr(int percent, int duration) { + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboSetTbrCmd") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION)); + return runCommand(new SetTbrCommand(percent, duration)); + } + + @Override + public CommandResult cancelTbr() { + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboCancelTbrCmd") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION)); + return runCommand(new CancelTbrCommand()); + } + + @Override + public CommandResult confirmAlert(int warningCode) { + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboConfirmAlertCmd") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION)); + return runCommand(new ConfirmAlertCommand(warningCode)); + } + + @Override + public CommandResult readHistory(PumpHistoryRequest request) { + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboReadHistoryCmd") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION)); + return runCommand(new ReadHistoryCommand(request)); + } + + @Override + public CommandResult readBasalProfile() { + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboReadBasalProfileCmd") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION)); + return runCommand(new ReadBasalProfileCommand()); + } + + @Override + public CommandResult setBasalProfile(BasalProfile basalProfile) { + FabricPrivacy.getInstance().logCustom(new CustomEvent("ComboSetBasalProfileCmd") + .putCustomAttribute("buildversion", BuildConfig.BUILDVERSION) + .putCustomAttribute("version", BuildConfig.VERSION)); + return runCommand(new SetBasalProfileCommand(basalProfile)); + } + + @Override + public CommandResult getDateAndTime() { + throw new RuntimeException("Not supported"); + } + + @Override + public CommandResult setDateAndTime() { + throw new RuntimeException("Not supported"); + } + + /** + * Confirms and dismisses the given alert if it's raised before the timeout + */ + public boolean confirmAlert(@NonNull Integer warningCode, int maxWaitMs) { + long timeout = System.currentTimeMillis() + maxWaitMs; + while (System.currentTimeMillis() < timeout) { + if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) { + WarningOrErrorCode warningOrErrorCode = readWarningOrErrorCode(); + if (warningOrErrorCode.errorCode != null) { + throw new CommandException("Pump is in error state"); + } + Integer displayedWarningCode = warningOrErrorCode.warningCode; + String errorMsg = null; + try { + errorMsg = (String) getCurrentMenu().getAttribute(MenuAttribute.MESSAGE); + } catch (Exception e) { + // ignore + } + if (!Objects.equals(displayedWarningCode, warningCode)) { + throw new CommandException("An alert other than the expected warning " + warningCode + " was raised by the pump: " + + displayedWarningCode + "(" + errorMsg + "). Please check the pump."); + } + + // confirm alert + verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR); + pressCheckKey(); + // dismiss alert + // if the user has confirmed the alert we have dismissed it with the button press + // above already, so only do that if an alert is still displayed + waitForScreenUpdate(); + if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) { + pressCheckKey(); + } + // wait till the pump has processed the alarm, otherwise it might still be showing + // when a command returns + WarningOrErrorCode displayedWarning = readWarningOrErrorCode(); + while (Objects.equals(displayedWarning.warningCode, warningCode)) { + waitForScreenUpdate(); + displayedWarning = readWarningOrErrorCode(); + } + return true; + } + SystemClock.sleep(10); + } + return false; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/WarningOrErrorCode.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/WarningOrErrorCode.java new file mode 100644 index 0000000000..a4e21da289 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/WarningOrErrorCode.java @@ -0,0 +1,27 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter; + +import android.support.annotation.Nullable; + +public class WarningOrErrorCode { + @Nullable + public final Integer warningCode; + @Nullable + public final Integer errorCode; + @Nullable + public String message; + + public WarningOrErrorCode(@Nullable Integer warningCode, @Nullable Integer errorCode, @Nullable String message) { + this.warningCode = warningCode; + this.errorCode = errorCode; + this.message = message; + } + + @Override + public String toString() { + return "WarningOrErrorCode{" + + "warningCode=" + warningCode + + ", errorCode=" + errorCode + + ", message=" + message + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/BaseCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/BaseCommand.java new file mode 100644 index 0000000000..5cc3e98b23 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/BaseCommand.java @@ -0,0 +1,85 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands; + +import android.support.annotation.NonNull; + +import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute; +import org.monkey.d.ruffy.ruffy.driver.display.MenuType; +import org.monkey.d.ruffy.ruffy.driver.display.menu.BolusType; +import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuDate; +import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime; + +import java.util.Calendar; +import java.util.Collections; +import java.util.List; + +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.CommandResult; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpWarningCodes; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.RuffyScripter; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Bolus; + +public abstract class BaseCommand implements Command { + // RS will inject itself here + protected RuffyScripter scripter; + + protected CommandResult result; + + public BaseCommand() { + result = new CommandResult(); + } + + @Override + public void setScripter(RuffyScripter scripter) { + this.scripter = scripter; + } + + @Override + public boolean needsRunMode() { + return true; + } + + /** + * A warning id (or null) caused by a disconnect we can safely confirm on reconnect, + * knowing it's not severe as it was caused by this command. + * @see PumpWarningCodes + */ + @Override + public Integer getReconnectWarningId() { + return null; + } + + @Override + public List validateArguments() { + return Collections.emptyList(); + } + + @Override + public CommandResult getResult() { + return result; + } + + @NonNull + protected Bolus readBolusRecord() { + scripter.verifyMenuIsDisplayed(MenuType.BOLUS_DATA); + BolusType bolusType = (BolusType) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_TYPE); + boolean isValid = bolusType == BolusType.NORMAL; + Double bolus = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS); + long recordDate = readRecordDate(); + return new Bolus(recordDate, bolus, isValid); + } + + protected long readRecordDate() { + MenuDate date = (MenuDate) scripter.getCurrentMenu().getAttribute(MenuAttribute.DATE); + MenuTime time = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.TIME); + + int year = Calendar.getInstance().get(Calendar.YEAR); + if (date.getMonth() > Calendar.getInstance().get(Calendar.MONTH) + 1) { + year -= 1; + } + Calendar calendar = Calendar.getInstance(); + calendar.set(year, date.getMonth() - 1, date.getDay(), time.getHour(), time.getMinute(), 0); + + // round to second + return calendar.getTimeInMillis() - calendar.getTimeInMillis() % 1000; + + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/BolusCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/BolusCommand.java new file mode 100644 index 0000000000..4188716553 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/BolusCommand.java @@ -0,0 +1,254 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands; + +import android.os.SystemClock; + +import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute; +import org.monkey.d.ruffy.ruffy.driver.display.MenuType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BolusProgressReporter; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.CommandResult; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpWarningCodes; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.WarningOrErrorCode; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Bolus; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.RuffyScripter; + +import static info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BolusProgressReporter.State.DELIVERED; +import static info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BolusProgressReporter.State.DELIVERING; +import static info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BolusProgressReporter.State.PROGRAMMING; +import static info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BolusProgressReporter.State.STOPPED; +import static info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BolusProgressReporter.State.STOPPING; + +public class BolusCommand extends BaseCommand { + private static final Logger log = LoggerFactory.getLogger(BolusCommand.class); + + protected final double bolus; + private final BolusProgressReporter bolusProgressReporter; + private volatile boolean cancelRequested; + + public BolusCommand(double bolus, BolusProgressReporter bolusProgressReporter) { + this.bolus = bolus; + this.bolusProgressReporter = bolusProgressReporter; + } + + @Override + public List validateArguments() { + List violations = new ArrayList<>(); + + if (bolus <= 0) { + violations.add("Requested bolus non-positive: " + bolus); + } + + return violations; + } + + @Override + public Integer getReconnectWarningId() { + return PumpWarningCodes.BOLUS_CANCELLED; + } + + @Override + public void execute() { + if (cancelRequested) { + bolusProgressReporter.report(STOPPED, 0, 0); + result.success = true; + log.debug("Stage 0: cancelled bolus before programming"); + return; + } + + bolusProgressReporter.report(PROGRAMMING, 0, 0); + enterBolusMenu(); + inputBolusAmount(); + verifyDisplayedBolusAmount(); + + // last chance to abort before confirming the bolus + if (cancelRequested) { + bolusProgressReporter.report(STOPPING, 0, 0); + scripter.returnToRootMenu(); + bolusProgressReporter.report(STOPPED, 0, 0); + result.success = true; + log.debug("Stage 1: cancelled bolus after programming"); + return; + } + + // confirm bolus + scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER); + scripter.pressCheckKey(); + log.debug("Stage 2: bolus confirmed"); + + // the pump displays the entered bolus and waits a few seconds to let user check and cancel + while (scripter.getCurrentMenu().getType() == MenuType.BOLUS_ENTER) { + if (cancelRequested) { + log.debug("Stage 2: cancelling during confirmation wait"); + bolusProgressReporter.report(STOPPING, 0, 0); + scripter.pressUpKey(); + // wait up to 1s for a BOLUS_CANCELLED alert, if it doesn't happen we missed + // the window, simply continue and let the next cancel attempt try its luck + boolean alertWasCancelled = scripter.confirmAlert(PumpWarningCodes.BOLUS_CANCELLED, 1000); + if (alertWasCancelled) { + log.debug("Stage 2: successfully cancelled during confirmation wait"); + bolusProgressReporter.report(STOPPED, 0, 0); + result.success = true; + return; + } + } + SystemClock.sleep(10); + } + + // the bolus progress is displayed on the main menu + scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU, + "Pump did not return to MAIN_MEU from BOLUS_ENTER to deliver bolus. " + + "Check pump manually, the bolus might not have been delivered."); + bolusProgressReporter.report(DELIVERING, 0, 0); + + // wait for bolus delivery to complete; the remaining units to deliver are counted down + boolean cancelInProgress = false; + Double lastBolusReported = 0d; + Double bolusRemaining = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING); + Thread cancellationThread = null; + while (bolusRemaining != null || scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) { + if (cancelRequested && !cancelInProgress) { + log.debug("Stage 3: cancellation while delivering bolus"); + bolusProgressReporter.report(STOPPING, 0, 0); + cancelInProgress = true; + cancellationThread = new Thread(() -> + scripter.pressKeyMs(RuffyScripter.Key.UP, 3000), "bolus-canceller"); + cancellationThread.start(); + } + if (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) { + // confirm warning alert and update the result to indicate alerts occurred + WarningOrErrorCode warningOrErrorCode = scripter.readWarningOrErrorCode(); + if (warningOrErrorCode.errorCode != null) { + throw new CommandException("Pump is in error state"); + } + Integer warningCode = warningOrErrorCode.warningCode; + if (Objects.equals(warningCode, PumpWarningCodes.BOLUS_CANCELLED)) { + // wait until cancellation thread releases the up button, otherwise we won't + // be able to confirm anything + if (cancellationThread != null) { + try { + cancellationThread.join(3500); + } catch (InterruptedException e) { + // ignore + } + } + scripter.confirmAlert(PumpWarningCodes.BOLUS_CANCELLED, 2000); + bolusProgressReporter.report(STOPPED, 0, 0); + log.debug("Stage 3: confirmed BOLUS CANCELLED after cancelling bolus during delivery"); + } else if (Objects.equals(warningCode, PumpWarningCodes.CARTRIDGE_LOW)) { + scripter.confirmAlert(PumpWarningCodes.CARTRIDGE_LOW, 2000); + result.forwardedWarnings.add(PumpWarningCodes.CARTRIDGE_LOW); + log.debug("Stage 3: confirmed low cartridge alert and forwarding to AAPS"); + } else if (Objects.equals(warningCode, PumpWarningCodes.BATTERY_LOW)) { + scripter.confirmAlert(PumpWarningCodes.BATTERY_LOW, 2000); + result.forwardedWarnings.add(PumpWarningCodes.BATTERY_LOW); + log.debug("Stage 3: confirmed low battery alert and forwarding to AAPS"); + } else { + // all other warnings or errors; + // An occlusion error can also occur during bolus. To read the partially delivered + // bolus, we'd have to first confirm the error. But an (occlusion) **error** shall not + // be confirmed and potentially be swallowed by a bug or shaky comms, so we let + // the pump be noisy (which the user will have to interact with anyway). + // Thus, this method will terminate with an exception and display an error message. + // Ideally, sometime after the user has dealt with the situation, the partially + // delivered bolus should be read. However, ready history is tricky at this point. + // Also: with an occlusion, the amount of insulin active is in question. + // It would be safer to assume the delivered bolus results in IOB, but there's + // only so much we can do at this point, so the user shall take over here and + // add a bolus record as and if needed. + throw new CommandException("Pump is showing exotic warning/error: " + warningOrErrorCode); + } + } + if (bolusRemaining != null && !Objects.equals(bolusRemaining, lastBolusReported)) { + log.debug("Delivering bolus, remaining: " + bolusRemaining); + int percentDelivered = (int) (100 - (bolusRemaining / bolus * 100)); + bolusProgressReporter.report(DELIVERING, percentDelivered, bolus - bolusRemaining); + lastBolusReported = bolusRemaining; + } + SystemClock.sleep(50); + bolusRemaining = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING); + } + // if a cancellation was started by pressing up for 3 seconds but the bolus has finished during those + // three seconds, must wait until the button is unpressed again so that follow up commands + // work properly. + if (cancellationThread != null) { + try { + cancellationThread.join(); + } catch (InterruptedException e) { + // ignore + } + } + + if (cancelInProgress) { + log.debug("Stage 4: bolus was cancelled, with unknown amount delivered"); + } else { + log.debug("Stage 4: full bolus of " + bolus + " U was successfully delivered"); + bolusProgressReporter.report(DELIVERED, 100, bolus); + } + result.success = true; + } + + private void enterBolusMenu() { + scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); + scripter.navigateToMenu(MenuType.BOLUS_MENU); + scripter.verifyMenuIsDisplayed(MenuType.BOLUS_MENU); + scripter.pressCheckKey(); + scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER); + } + + private void inputBolusAmount() { + scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER); + // press 'up' once for each 0.1 U increment + long steps = Math.round(bolus * 10); + for (int i = 0; i < steps; i++) { + scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER); + scripter.pressUpKey(); + SystemClock.sleep(50); + } + } + + private void verifyDisplayedBolusAmount() { + scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER); + + // wait up to 10s for any scrolling to finish + double displayedBolus = scripter.readBlinkingValue(Double.class, MenuAttribute.BOLUS); + long timeout = System.currentTimeMillis() + 10 * 1000; + while (timeout > System.currentTimeMillis() && bolus - displayedBolus > 0.05) { + log.debug("Waiting for pump to process scrolling input for amount, current: " + displayedBolus + ", desired: " + bolus); + SystemClock.sleep(50); + displayedBolus = scripter.readBlinkingValue(Double.class, MenuAttribute.BOLUS); + } + + log.debug("Final bolus: " + displayedBolus); + if (Math.abs(displayedBolus - bolus) > 0.01) { + throw new CommandException("Failed to set correct bolus. Expected: " + bolus + ", actual: " + displayedBolus); + } + + // check again to ensure the displayed value hasn't change due to due scrolling taking extremely long + SystemClock.sleep(1000); + scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER); + double refreshedDisplayedBolus = scripter.readBlinkingValue(Double.class, MenuAttribute.BOLUS); + if (Math.abs(displayedBolus - refreshedDisplayedBolus) > 0.01) { + throw new CommandException("Failed to set bolus: bolus changed after input stopped from " + + displayedBolus + " -> " + refreshedDisplayedBolus); + } + } + + public void requestCancellation() { + log.debug("Bolus cancellation requested"); + cancelRequested = true; + bolusProgressReporter.report(STOPPING, 0, 0); + } + + @Override + public String toString() { + return "BolusCommand{" + + "bolus=" + bolus + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/CancelTbrCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/CancelTbrCommand.java new file mode 100644 index 0000000000..b1787005e1 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/CancelTbrCommand.java @@ -0,0 +1,41 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands; + +import org.monkey.d.ruffy.ruffy.driver.display.MenuType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpState; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpWarningCodes; + +public class CancelTbrCommand extends BaseCommand { + private static final Logger log = LoggerFactory.getLogger(CancelTbrCommand.class); + + @Override + public Integer getReconnectWarningId() { + return PumpWarningCodes.TBR_CANCELLED; + } + + @Override + public void execute() { + scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); + PumpState pumpState = scripter.readPumpStateInternal(); + if (!pumpState.tbrActive) { + // This is non-critical; when cancelling a TBR and the connection was interrupted + // the TBR was cancelled by that. In that case not cancelling anything is fine. + result.success = true; + return; + } + + log.debug("Cancelling active TBR of " + pumpState.tbrPercent + + "% with " + pumpState.tbrRemainingDuration + " min remaining"); + SetTbrCommand setTbrCommand = new SetTbrCommand(100, 0); + setTbrCommand.setScripter(scripter); + setTbrCommand.execute(); + result = setTbrCommand.result; + } + + @Override + public String toString() { + return "CancelTbrCommand{}"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/Command.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/Command.java new file mode 100644 index 0000000000..152befe00d --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/Command.java @@ -0,0 +1,22 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands; + +import java.util.List; + +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.RuffyScripter; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.CommandResult; + +/** + * Interface for all commands to be executed by the pump. + *

+ * Note on cammond methods and timing: a method shall wait before and after executing + * as necessary to not cause timing issues, so the caller can just call methods in + * sequence, letting the methods take care of waits. + */ +public interface Command { + void setScripter(RuffyScripter scripter); + List validateArguments(); + boolean needsRunMode(); + void execute(); + CommandResult getResult(); + Integer getReconnectWarningId(); +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/CommandException.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/CommandException.java new file mode 100644 index 0000000000..77c1db5437 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/CommandException.java @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands; + +public class CommandException extends RuntimeException { + public CommandException(String message) { + super(message); + } + + public CommandException(String message, Exception exception) { + super(message, exception); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/ConfirmAlertCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/ConfirmAlertCommand.java new file mode 100644 index 0000000000..636b6a624d --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/ConfirmAlertCommand.java @@ -0,0 +1,26 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands; + +public class ConfirmAlertCommand extends BaseCommand { + private final int warningCode; + + public ConfirmAlertCommand(int warningCode) { + this.warningCode = warningCode; + } + + @Override + public void execute() { + result.success(scripter.confirmAlert(warningCode, 5000)); + } + + @Override + public boolean needsRunMode() { + return false; + } + + @Override + public String toString() { + return "ConfirmAlertCommand{" + + "warningCode=" + warningCode + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/ReadBasalProfileCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/ReadBasalProfileCommand.java new file mode 100644 index 0000000000..2c23d5814b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/ReadBasalProfileCommand.java @@ -0,0 +1,62 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands; + +import org.monkey.d.ruffy.ruffy.driver.display.Menu; +import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute; +import org.monkey.d.ruffy.ruffy.driver.display.MenuType; +import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; + +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BasalProfile; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpState; + +public class ReadBasalProfileCommand extends BaseCommand { + private static final Logger log = LoggerFactory.getLogger(ReadBasalProfileCommand.class); + + @Override + public void execute() { + scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); + if (scripter.readPumpStateInternal().unsafeUsageDetected == PumpState.UNSUPPORTED_BASAL_RATE_PROFILE) { + throw new CommandException("Active basal rate profile != 1"); + } + scripter.navigateToMenu(MenuType.BASAL_1_MENU); + scripter.verifyMenuIsDisplayed(MenuType.BASAL_1_MENU); + scripter.pressCheckKey(); + + BasalProfile basalProfile = new BasalProfile(); + + // summary screen is shown; press menu to page through hours, wraps around to summary; + scripter.verifyMenuIsDisplayed(MenuType.BASAL_TOTAL); + for (int i = 0; i < 24; i++) { + scripter.pressMenuKey(); + Menu menu = scripter.getCurrentMenu(); + while (menu.getType() != MenuType.BASAL_SET + || ((MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.BASAL_START)).getHour() != i) { + scripter.waitForScreenUpdate(); + menu = scripter.getCurrentMenu(); + } + scripter.verifyMenuIsDisplayed(MenuType.BASAL_SET); + + MenuTime startTime = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.BASAL_START); + if (i != startTime.getHour()) { + throw new CommandException("Attempting to read basal rate for hour " + i + ", but hour " + startTime.getHour() + " is displayed"); + } + basalProfile.hourlyRates[i] = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE); + log.debug("Read basal profile, hour " + i + ": " + basalProfile.hourlyRates[i]); + } + + log.debug("Basal profile read: " + Arrays.toString(basalProfile.hourlyRates)); + + scripter.returnToRootMenu(); + scripter.verifyRootMenuIsDisplayed(); + + result.success(true).basalProfile(basalProfile); + } + + @Override + public String toString() { + return "ReadBasalProfileCommand{}"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/ReadHistoryCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/ReadHistoryCommand.java new file mode 100644 index 0000000000..04298acd56 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/ReadHistoryCommand.java @@ -0,0 +1,275 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands; + +import android.support.annotation.NonNull; + +import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute; +import org.monkey.d.ruffy.ruffy.driver.display.MenuType; +import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuDate; +import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Calendar; +import java.util.Date; + +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Bolus; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpAlert; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpHistory; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpHistoryRequest; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Tbr; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Tdd; + +public class ReadHistoryCommand extends BaseCommand { + private static Logger log = LoggerFactory.getLogger(ReadHistoryCommand.class); + + private final PumpHistoryRequest request; + private final PumpHistory history = new PumpHistory(); + + public ReadHistoryCommand(PumpHistoryRequest request) { + this.request = request; + } + + @Override + public void execute() { + if (request.bolusHistory == PumpHistoryRequest.SKIP + && request.tbrHistory == PumpHistoryRequest.SKIP + && request.pumpErrorHistory == PumpHistoryRequest.SKIP + && request.tddHistory == PumpHistoryRequest.SKIP) { + throw new CommandException("History request but all data types are skipped"); + } + + scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); + scripter.navigateToMenu(MenuType.MY_DATA_MENU); + scripter.verifyMenuIsDisplayed(MenuType.MY_DATA_MENU); + scripter.pressCheckKey(); + + // bolus history + scripter.verifyMenuIsDisplayed(MenuType.BOLUS_DATA); + if (request.bolusHistory != PumpHistoryRequest.SKIP) { + int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD); + if (totalRecords > 0) { + if (request.bolusHistory == PumpHistoryRequest.LAST) { + Bolus bolus = readBolusRecord(); + history.bolusHistory.add(bolus); + } else { + readBolusRecords(request.bolusHistory); + } + } + } + + if (request.pumpErrorHistory != PumpHistoryRequest.SKIP + || request.tddHistory != PumpHistoryRequest.SKIP + || request.tbrHistory != PumpHistoryRequest.SKIP) { + // error history + scripter.pressMenuKey(); + scripter.verifyMenuIsDisplayed(MenuType.ERROR_DATA); + if (request.pumpErrorHistory != PumpHistoryRequest.SKIP) { + int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD); + if (totalRecords > 0) { + if (request.pumpErrorHistory == PumpHistoryRequest.LAST) { + PumpAlert error = readAlertRecord(); + history.pumpAlertHistory.add(error); + } else { + readAlertRecords(request.pumpErrorHistory); + } + } + } + + // tdd history (TBRs are added to history only after they've completed running) + scripter.pressMenuKey(); + scripter.verifyMenuIsDisplayed(MenuType.DAILY_DATA); + if (request.tddHistory != PumpHistoryRequest.SKIP) { + int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD); + if (totalRecords > 0) { + if (request.tddHistory == PumpHistoryRequest.LAST) { + Tdd tdd = readTddRecord(); + history.tddHistory.add(tdd); + } else { + readTddRecords(request.tbrHistory); + } + } + } + + // tbr history + scripter.pressMenuKey(); + scripter.verifyMenuIsDisplayed(MenuType.TBR_DATA); + if (request.tbrHistory != PumpHistoryRequest.SKIP) { + int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD); + if (totalRecords > 0) { + if (request.tbrHistory == PumpHistoryRequest.LAST) { + Tbr tbr = readTbrRecord(); + history.tbrHistory.add(tbr); + } else { + readTbrRecords(request.tbrHistory); + } + } + } + } + + if (log.isDebugEnabled()) { + if (!history.bolusHistory.isEmpty()) { + log.debug("Read bolus history (" + history.bolusHistory.size() + "):"); + for (Bolus bolus : history.bolusHistory) { + log.debug(new Date(bolus.timestamp) + ": " + bolus.toString()); + } + } + if (!history.pumpAlertHistory.isEmpty()) { + log.debug("Read error history (" + history.pumpAlertHistory.size() + "):"); + for (PumpAlert pumpAlert : history.pumpAlertHistory) { + log.debug(new Date(pumpAlert.timestamp) + ": " + pumpAlert.toString()); + } + } + if (!history.tddHistory.isEmpty()) { + log.debug("Read TDD history (" + history.tddHistory.size() + "):"); + for (Tdd tdd : history.tddHistory) { + log.debug(new Date(tdd.timestamp) + ": " + tdd.toString()); + } + } + if (!history.tbrHistory.isEmpty()) { + log.debug("Read TBR history (" + history.tbrHistory.size() + "):"); + for (Tbr tbr : history.tbrHistory) { + log.debug(new Date(tbr.timestamp) + ": " + tbr.toString()); + } + } + } + + scripter.returnToRootMenu(); + scripter.verifyRootMenuIsDisplayed(); + + result.success(true).history(history); + } + + private void readTddRecords(long requestedTime) { + int record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD); + int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD); + while (true) { + Tdd tdd = readTddRecord(); + if (requestedTime != PumpHistoryRequest.FULL && tdd.timestamp < requestedTime) { + break; + } + log.debug("Read TDD record #" + record + "/" + totalRecords); + history.tddHistory.add(tdd); + log.debug("Parsed " + scripter.getCurrentMenu().toString() + " => " + tdd); + if (record == totalRecords) { + break; + } + scripter.pressDownKey(); + while (record == (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD)) { + scripter.waitForScreenUpdate(); + } + record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD); + } + } + + @NonNull + private Tdd readTddRecord() { + scripter.verifyMenuIsDisplayed(MenuType.DAILY_DATA); + Double dailyTotal = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.DAILY_TOTAL); + MenuDate date = (MenuDate) scripter.getCurrentMenu().getAttribute(MenuAttribute.DATE); + + int year = Calendar.getInstance().get(Calendar.YEAR); + if (date.getMonth() > Calendar.getInstance().get(Calendar.MONTH) + 1) { + year -= 1; + } + Calendar calendar = Calendar.getInstance(); + calendar.set(year, date.getMonth() - 1, date.getDay(), 0, 0, 0); + long time = calendar.getTimeInMillis(); + time = time - time%1000; + return new Tdd(time, dailyTotal); + } + + private void readTbrRecords(long requestedTime) { + int record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD); + int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD); + while (true) { + Tbr tbr = readTbrRecord(); + if (requestedTime != PumpHistoryRequest.FULL && tbr.timestamp < requestedTime) { + break; + } + log.debug("Read TBR record #" + record + "/" + totalRecords); + history.tbrHistory.add(tbr); + log.debug("Parsed " + scripter.getCurrentMenu().toString() + " => " + tbr); + if (record == totalRecords) { + break; + } + scripter.pressDownKey(); + while (record == (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD)) { + scripter.waitForScreenUpdate(); + } + record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD); + } + } + + @NonNull + private Tbr readTbrRecord() { + scripter.verifyMenuIsDisplayed(MenuType.TBR_DATA); + Double percentage = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.TBR); + MenuTime durationTime = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.RUNTIME); + int duration = durationTime.getHour() * 60 + durationTime.getMinute(); + long tbrStartDate = readRecordDate() - duration * 60 * 1000; + return new Tbr(tbrStartDate, duration, percentage.intValue()); + } + + private void readBolusRecords(long requestedTime) { + int record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD); + int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD); + while (true) { + Bolus bolus = readBolusRecord(); + if (requestedTime != PumpHistoryRequest.FULL && bolus.timestamp < requestedTime) { + break; + } + log.debug("Read bolus record #" + record + "/" + totalRecords); + history.bolusHistory.add(bolus); + log.debug("Parsed " + scripter.getCurrentMenu().toString() + " => " + bolus); + if (record == totalRecords) { + break; + } + scripter.pressDownKey(); + while (record == (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD)) { + scripter.waitForScreenUpdate(); + } + record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD); + } + } + + private void readAlertRecords(long requestedTime) { + int record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD); + int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD); + while (true) { + PumpAlert error = readAlertRecord(); + if (requestedTime != PumpHistoryRequest.FULL && error.timestamp < requestedTime) { + break; + } + log.debug("Read alert record #" + record + "/" + totalRecords); + history.pumpAlertHistory.add(error); + log.debug("Parsed " + scripter.getCurrentMenu().toString() + " => " + error); + if (record == totalRecords) { + break; + } + scripter.pressDownKey(); + while (record == (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD)) { + scripter.waitForScreenUpdate(); + } + record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD); + } + } + + @NonNull + private PumpAlert readAlertRecord() { + scripter.verifyMenuIsDisplayed(MenuType.ERROR_DATA); + + Integer warningCode = (Integer) scripter.getCurrentMenu().getAttribute(MenuAttribute.WARNING); + Integer errorCode = (Integer) scripter.getCurrentMenu().getAttribute(MenuAttribute.ERROR); + String message = (String) scripter.getCurrentMenu().getAttribute(MenuAttribute.MESSAGE); + long recordDate = readRecordDate(); + return new PumpAlert(recordDate, warningCode, errorCode, message); + } + + @Override + public String toString() { + return "ReadHistoryCommand{" + + "request=" + request + + ", history=" + history + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/ReadPumpStateCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/ReadPumpStateCommand.java new file mode 100644 index 0000000000..cfa8d0f329 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/ReadPumpStateCommand.java @@ -0,0 +1,19 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands; + +public class ReadPumpStateCommand extends BaseCommand { + @Override + public void execute() { + // nothing to do, scripter adds state to all command results + result.success = true; + } + + @Override + public String toString() { + return "ReadPumpStateCommand{}"; + } + + @Override + public boolean needsRunMode() { + return false; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/ReadQuickInfoCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/ReadQuickInfoCommand.java new file mode 100644 index 0000000000..e55fb4d010 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/ReadQuickInfoCommand.java @@ -0,0 +1,76 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands; + +import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute; +import org.monkey.d.ruffy.ruffy.driver.display.MenuType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.Bolus; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history.PumpHistory; + +public class ReadQuickInfoCommand extends BaseCommand { + private static final Logger log = LoggerFactory.getLogger(ReadQuickInfoCommand.class); + + private final int numberOfBolusRecordsToRetrieve; + + public ReadQuickInfoCommand(int numberOfBolusRecordsToRetrieve) { + this.numberOfBolusRecordsToRetrieve = numberOfBolusRecordsToRetrieve; + } + + @Override + public void execute() { + scripter.verifyRootMenuIsDisplayed(); + // navigate to reservoir menu + scripter.pressCheckKey(); + scripter.waitForMenuToBeLeft(MenuType.MAIN_MENU); + scripter.waitForMenuToBeLeft(MenuType.STOP); + scripter.verifyMenuIsDisplayed(MenuType.QUICK_INFO); + result.reservoirLevel = ((Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.REMAINING_INSULIN)).intValue(); + if (numberOfBolusRecordsToRetrieve > 0) { + // navigate to bolus data menu + scripter.pressCheckKey(); + scripter.verifyMenuIsDisplayed(MenuType.BOLUS_DATA); + List bolusHistory = new ArrayList<>(numberOfBolusRecordsToRetrieve); + result.history = new PumpHistory().bolusHistory(bolusHistory); + // read bolus records + int totalRecords = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.TOTAL_RECORD); + int record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD); + while (true) { + bolusHistory.add(readBolusRecord()); + if (bolusHistory.size() == numberOfBolusRecordsToRetrieve || record == totalRecords) { + break; + } + // advance to next record + scripter.pressDownKey(); + while (record == (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD)) { + scripter.waitForScreenUpdate(); + } + record = (int) scripter.getCurrentMenu().getAttribute(MenuAttribute.CURRENT_RECORD); + } + if (log.isDebugEnabled()) { + if (!result.history.bolusHistory.isEmpty()) { + log.debug("Read bolus history (" + result.history.bolusHistory.size() + "):"); + for (Bolus bolus : result.history.bolusHistory) { + log.debug(new Date(bolus.timestamp) + ": " + bolus.toString()); + } + } + } + } + scripter.returnToRootMenu(); + result.success = true; + } + + @Override + public boolean needsRunMode() { + return false; + } + + @Override + public String toString() { + return "ReadQuickInfoCommand{}"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/SetBasalProfileCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/SetBasalProfileCommand.java new file mode 100644 index 0000000000..f403c2a89b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/SetBasalProfileCommand.java @@ -0,0 +1,162 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands; + +import android.os.SystemClock; + +import org.monkey.d.ruffy.ruffy.driver.display.Menu; +import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute; +import org.monkey.d.ruffy.ruffy.driver.display.MenuType; +import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.BasalProfile; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpState; + +public class SetBasalProfileCommand extends BaseCommand { + private static final Logger log = LoggerFactory.getLogger(SetBasalProfileCommand.class); + + private final BasalProfile basalProfile; + + public SetBasalProfileCommand(BasalProfile basalProfile) { + this.basalProfile = basalProfile; + } + + @Override + public void execute() { + scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); + if (scripter.readPumpStateInternal().unsafeUsageDetected == PumpState.UNSUPPORTED_BASAL_RATE_PROFILE) { + throw new CommandException("Active basal rate profile != 1"); + } + scripter.navigateToMenu(MenuType.BASAL_1_MENU); + scripter.verifyMenuIsDisplayed(MenuType.BASAL_1_MENU); + scripter.pressCheckKey(); + + // summary screen is shown; press menu to page through hours, wraps around to summary; + scripter.verifyMenuIsDisplayed(MenuType.BASAL_TOTAL); + for (int i = 0; i < 24; i++) { + scripter.pressMenuKey(); + Menu menu = scripter.getCurrentMenu(); + while (menu.getType() != MenuType.BASAL_SET + || ((MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.BASAL_START)).getHour() != i) { + scripter.waitForScreenUpdate(); + menu = scripter.getCurrentMenu(); + } + scripter.verifyMenuIsDisplayed(MenuType.BASAL_SET); + + double requestedRate = basalProfile.hourlyRates[i]; + long change = inputBasalRate(requestedRate); + if (change != 0) { + verifyDisplayedRate(requestedRate, change); + } + + log.debug("Set basal profile, hour " + i + ": " + requestedRate); + } + + // move from hourly values to basal total + scripter.pressCheckKey(); + scripter.verifyMenuIsDisplayed(MenuType.BASAL_TOTAL); + + // check total basal total on pump matches requested total + Double pumpTotal = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BASAL_TOTAL); + Double requestedTotal = 0d; + for (int i = 0; i < 24; i++) { + requestedTotal += basalProfile.hourlyRates[i]; + } + if (Math.abs(pumpTotal - requestedTotal) > 0.001) { + throw new CommandException("Basal total of " + pumpTotal + " differs from requested total of " + requestedTotal); + } + + // confirm entered basal rate + scripter.pressCheckKey(); + scripter.verifyRootMenuIsDisplayed(); + + result.success(true).basalProfile(basalProfile); + } + + private long inputBasalRate(double requestedRate) { + double currentRate = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE); + log.debug("Current rate: " + currentRate + ", requested: " + requestedRate); + // the pump changes steps size from 0.01 to 0.05 when crossing 1.00 U + long steps = 0; + if (currentRate == 0) { + // edge case of starting from 0.00; + steps = stepsToOne(0.05) - stepsToOne(requestedRate) + 1; + } else { + steps = stepsToOne(currentRate) - stepsToOne(requestedRate); + } + if (steps == 0) { + return 0; + } + log.debug("Pressing " + (steps > 0 ? "up" : "down") + " " + Math.abs(steps) + " times"); + for (int i = 0; i < Math.abs(steps); i++) { + scripter.verifyMenuIsDisplayed(MenuType.BASAL_SET); + log.debug("Push #" + (i + 1) + "/" + Math.abs(steps)); + if (steps > 0) scripter.pressUpKey(); + else scripter.pressDownKey(); + SystemClock.sleep(50); + } + return steps; + } + + /** + * Steps required to go up to 1.0 (positive return value), + * or down to 1.0 (negative return value). + */ + private long stepsToOne(double rate) { + double change = (1.0 - rate); + if (rate > 1) return Math.round(change / 0.05); + return Math.round(change / 0.01); + } + + private void verifyDisplayedRate(double requestedRate, long change) { + scripter.verifyMenuIsDisplayed(MenuType.BASAL_SET); + // wait up to 5s for any scrolling to finish + double displayedRate = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE); + long timeout = System.currentTimeMillis() + 10 * 1000; + while (timeout > System.currentTimeMillis() + && ((change > 0 && requestedRate - displayedRate > 0.001) // displayedRate < requestedRate) + || (change < 0 && displayedRate - requestedRate > 0.001))) { //displayedRate > requestedRate))) { + log.debug("Waiting for pump to process scrolling input for rate, current: " + + displayedRate + ", desired: " + requestedRate + ", scrolling " + + (change > 0 ? "up" : "down")); + scripter.waitForScreenUpdate(); + displayedRate = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE); + } + log.debug("Final displayed basal rate: " + displayedRate); + if (Math.abs(displayedRate - requestedRate) > 0.001) { + throw new CommandException("Failed to set basal rate, requested: " + + requestedRate + ", actual: " + displayedRate); + } + + // check again to ensure the displayed value hasn't change and scrolled past the desired + // value due to due scrolling taking extremely long + SystemClock.sleep(1000); + scripter.verifyMenuIsDisplayed(MenuType.BASAL_SET); + double refreshedDisplayedRate = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE); + if (Math.abs(displayedRate - refreshedDisplayedRate) > 0.001) { + throw new CommandException("Failed to set basal rate: " + + "percentage changed after input stopped from " + + displayedRate + " -> " + refreshedDisplayedRate); + } + } + + @Override + public List validateArguments() { + ArrayList violations = new ArrayList<>(); + if (basalProfile == null) { + violations.add("No basal profile supplied"); + } + + return violations; + } + + @Override + public String toString() { + return "SetBasalProfileCommand{" + + "basalProfile=" + basalProfile + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/SetTbrCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/SetTbrCommand.java new file mode 100644 index 0000000000..f618d50156 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/commands/SetTbrCommand.java @@ -0,0 +1,286 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.commands; + +import android.os.SystemClock; + +import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute; +import org.monkey.d.ruffy.ruffy.driver.display.MenuType; +import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpState; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.PumpWarningCodes; +import info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.WarningOrErrorCode; + +public class SetTbrCommand extends BaseCommand { + private static final Logger log = LoggerFactory.getLogger(SetTbrCommand.class); + + private final long percentage; + private final long duration; + + public SetTbrCommand(long percentage, long duration) { + this.percentage = percentage; + this.duration = duration; + } + + @Override + public List validateArguments() { + List violations = new ArrayList<>(); + + if (percentage % 10 != 0) { + violations.add("TBR percentage must be set in 10% steps"); + } + if (percentage < 0 || percentage > 500) { + violations.add("TBR percentage must be within 0-500%"); + } + + if (percentage != 100) { + if (duration % 15 != 0) { + violations.add("TBR duration can only be set in 15 minute steps"); + } + if (duration > 60 * 24) { + violations.add("Maximum TBR duration is 24 hours"); + } + } + + return violations; + } + + @Override + public Integer getReconnectWarningId() { + return PumpWarningCodes.TBR_CANCELLED; + } + + @Override + public void execute() { + try { + if (checkAndWaitIfExistingTbrIsAboutToEnd()) { + return; + } + + enterTbrMenu(); + boolean increasingPercentage = inputTbrPercentage(); + verifyDisplayedTbrPercentage(increasingPercentage); + + if (percentage == 100) { + cancelTbrAndConfirmCancellationWarning(); + } else { + // switch to TBR_DURATION menu by pressing menu key + scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); + scripter.pressMenuKey(); + scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); + + boolean increasingDuration = inputTbrDuration(); + verifyDisplayedTbrDuration(increasingDuration); + + // confirm TBR + scripter.pressCheckKey(); + scripter.waitForMenuToBeLeft(MenuType.TBR_DURATION); + } + } catch (CommandException e) { + if (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) { + // The pump raises a TBR CANCELLED warning when a running TBR finishes while we're + // programming a new one (TBR remaining time was last displayed as 0:01, the pump + // rounds seconds up to full minutes). In that case confirm the alert since we know + // we caused it (in a way), but still fail the command so the usual cleanups of returning + // to main menu etc are performed, after which this command can simply be retried. + // Note that this situation should have been dealt with in + // #checkAndWaitIfExistingTbrIsAboutToEnd, but still occur if that method runs + // into a timeout or some other freaky thing happens, so we'll leave it here. + WarningOrErrorCode warningOrErrorCode = scripter.readWarningOrErrorCode(); + if (Objects.equals(warningOrErrorCode.warningCode, PumpWarningCodes.TBR_CANCELLED)) { + scripter.confirmAlert(PumpWarningCodes.TBR_CANCELLED); + } + } + throw e; + } + + result.success = true; + } + + /** + * When programming a new TBR while an existing TBR runs out, a TBR CANCELLED + * alert is raised (failing the command, requiring a reconnect and confirming alert + * and all). To avoid this, wait until the active TBR runs out if the active TBR + * is about to end. + * + * @return true if we waited till the TBR ended and cancellation was request so all work is done. + */ + private boolean checkAndWaitIfExistingTbrIsAboutToEnd() { + scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); + long timeout = System.currentTimeMillis() + 65 * 1000; + PumpState state = scripter.readPumpStateInternal(); + if (state.tbrRemainingDuration == 1) { + while (state.tbrActive && System.currentTimeMillis() < timeout) { + log.debug("Waiting for existing TBR to run out to avoid alert while setting TBR"); + scripter.waitForScreenUpdate(); + state = scripter.readPumpStateInternal(); + } + // if we waited above and a cancellation was requested, we already completed the request + if (!state.tbrActive && percentage == 100) { + result.success = true; + return true; + } + } + return false; + } + + private void enterTbrMenu() { + scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); + scripter.navigateToMenu(MenuType.TBR_MENU); + scripter.verifyMenuIsDisplayed(MenuType.TBR_MENU); + scripter.pressCheckKey(); + scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); + } + + private boolean inputTbrPercentage() { + scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); + long currentPercent = readDisplayedPercentage(); + log.debug("Current TBR %: " + currentPercent); + long percentageChange = percentage - currentPercent; + long percentageSteps = percentageChange / 10; + boolean increasePercentage = percentageSteps > 0; + log.debug("Pressing " + (increasePercentage ? "up" : "down") + " " + percentageSteps + " times"); + for (int i = 0; i < Math.abs(percentageSteps); i++) { + scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); + log.debug("Push #" + (i + 1) + "/" + Math.abs(percentageSteps)); + if (increasePercentage) scripter.pressUpKey(); + else scripter.pressDownKey(); + SystemClock.sleep(50); + } + return increasePercentage; + } + + private void verifyDisplayedTbrPercentage(boolean increasingPercentage) { + scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); + // wait up to 5s for any scrolling to finish + long displayedPercentage = readDisplayedPercentage(); + long timeout = System.currentTimeMillis() + 10 * 1000; + while (timeout > System.currentTimeMillis() + && ((increasingPercentage && displayedPercentage < percentage) + || (!increasingPercentage && displayedPercentage > percentage))) { + log.debug("Waiting for pump to process scrolling input for percentage, current: " + + displayedPercentage + ", desired: " + percentage + ", scrolling " + + (increasingPercentage ? "up" : "down")); + SystemClock.sleep(50); + displayedPercentage = readDisplayedPercentage(); + } + log.debug("Final displayed TBR percentage: " + displayedPercentage); + if (displayedPercentage != percentage) { + throw new CommandException("Failed to set TBR percentage, requested: " + + percentage + ", actual: " + displayedPercentage); + } + + // check again to ensure the displayed value hasn't change and scrolled past the desired + // value due to due scrolling taking extremely long + SystemClock.sleep(1000); + scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); + long refreshedDisplayedTbrPecentage = readDisplayedPercentage(); + if (displayedPercentage != refreshedDisplayedTbrPecentage) { + throw new CommandException("Failed to set TBR percentage: " + + "percentage changed after input stopped from " + + displayedPercentage + " -> " + refreshedDisplayedTbrPecentage); + } + } + + private boolean inputTbrDuration() { + scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); + long durationSteps = calculateDurationSteps(); + boolean increaseDuration = durationSteps > 0; + log.debug("Pressing " + (increaseDuration ? "up" : "down") + " " + durationSteps + " times"); + for (int i = 0; i < Math.abs(durationSteps); i++) { + scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); + log.debug("Push #" + (i + 1) + "/" + Math.abs(durationSteps)); + if (increaseDuration) scripter.pressUpKey(); + else scripter.pressDownKey(); + SystemClock.sleep(50); + } + return increaseDuration; + } + + private long calculateDurationSteps() { + long currentDuration = readDisplayedDuration(); + log.debug("Initial TBR duration: " + currentDuration); + + long difference = duration - currentDuration; + long durationSteps = difference / 15; + long durationAfterInitialSteps = currentDuration + (durationSteps * 15); + + if (durationAfterInitialSteps < duration) return durationSteps + 1; + else if (durationAfterInitialSteps > duration) return durationSteps - 1; + else return durationSteps; + } + + private void verifyDisplayedTbrDuration(boolean increasingPercentage) { + scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); + + // wait up to 5s for any scrolling to finish + long displayedDuration = readDisplayedDuration(); + long timeout = System.currentTimeMillis() + 10 * 1000; + while (timeout > System.currentTimeMillis() + && ((increasingPercentage && displayedDuration < duration) + || (!increasingPercentage && displayedDuration > duration))) { + log.debug("Waiting for pump to process scrolling input for duration, current: " + + displayedDuration + ", desired: " + duration + + ", scrolling " + (increasingPercentage ? "up" : "down")); + SystemClock.sleep(50); + displayedDuration = readDisplayedDuration(); + } + + log.debug("Final displayed TBR duration: " + displayedDuration); + if (displayedDuration != duration) { + throw new CommandException("Failed to set TBR duration, requested: " + + duration + ", actual: " + displayedDuration); + } + + // check again to ensure the displayed value hasn't change and scrolled past the desired + // value due to due scrolling taking extremely long + SystemClock.sleep(1000); + scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); + long refreshedDisplayedTbrDuration = readDisplayedDuration(); + if (displayedDuration != refreshedDisplayedTbrDuration) { + throw new CommandException("Failed to set TBR duration: " + + "duration changed after input stopped from " + + displayedDuration + " -> " + refreshedDisplayedTbrDuration); + } + } + + private void cancelTbrAndConfirmCancellationWarning() { + // confirm entered TBR + scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); + scripter.pressCheckKey(); + + // A "TBR CANCELLED alert" is only raised by the pump when the remaining time is + // greater than 60s (displayed as 0:01, the pump goes from there to finished. + // We could read the remaining duration from MAIN_MENU, but by the time we're here, + // the pump could have moved from 0:02 to 0:01, so instead, check if a "TBR CANCELLED" alert + // is raised and if so dismiss it + scripter.confirmAlert(PumpWarningCodes.TBR_CANCELLED, 2000); + } + + private long readDisplayedDuration() { + MenuTime duration = scripter.readBlinkingValue(MenuTime.class, MenuAttribute.RUNTIME); + return duration.getHour() * 60 + duration.getMinute(); + } + + private long readDisplayedPercentage() { + return scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE).longValue(); + } + + @Override + public boolean needsRunMode() { + return true; + } + + @Override + public String toString() { + return "SetTbrCommand{" + + "percentage=" + percentage + + ", duration=" + duration + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/Bolus.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/Bolus.java new file mode 100644 index 0000000000..a5d989c331 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/Bolus.java @@ -0,0 +1,46 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history; + +import java.util.Date; + +public class Bolus extends HistoryRecord { + public final double amount; + public final boolean isValid; + + public Bolus(long timestamp, double amount, boolean isValid) { + super(timestamp); + this.amount = amount; + this.isValid = isValid; + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Bolus bolus = (Bolus) o; + + if (timestamp != bolus.timestamp) return false; + if (isValid != bolus.isValid) return false; + return Math.abs(bolus.amount - amount) <= 0.01; + } + + @Override + public int hashCode() { + int result; + long temp; + result = (int) (timestamp ^ (timestamp >>> 32)); + temp = Double.doubleToLongBits(amount); + result = result + (isValid ? 1 : 0); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public String toString() { + return "Bolus{" + + "timestamp=" + timestamp + " (" + new Date(timestamp) + ")" + + ", amount=" + amount + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/HistoryRecord.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/HistoryRecord.java new file mode 100644 index 0000000000..57e173bb49 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/HistoryRecord.java @@ -0,0 +1,9 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history; + +public abstract class HistoryRecord { + public final long timestamp; + + protected HistoryRecord(long timestamp) { + this.timestamp = timestamp; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/PumpAlert.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/PumpAlert.java new file mode 100644 index 0000000000..413a9ca69b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/PumpAlert.java @@ -0,0 +1,51 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history; + +import java.util.Date; + +public class PumpAlert extends HistoryRecord { + public final Integer warningCode; + public final Integer errorCode; + /** Error message, in the language configured on the pump. */ + public final String message; + + public PumpAlert(long timestamp, Integer warningCode, Integer errorCode, String message) { + super(timestamp); + this.warningCode = warningCode; + this.errorCode = errorCode; + this.message = message; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + PumpAlert pumpAlert = (PumpAlert) o; + + if (timestamp != pumpAlert.timestamp) return false; + if (warningCode != null ? !warningCode.equals(pumpAlert.warningCode) : pumpAlert.warningCode != null) + return false; + if (errorCode != null ? !errorCode.equals(pumpAlert.errorCode) : pumpAlert.errorCode != null) + return false; + return message != null ? message.equals(pumpAlert.message) : pumpAlert.message == null; + } + + @Override + public int hashCode() { + int result = (int) (timestamp ^ (timestamp >>> 32)); + result = 31 * result + (warningCode != null ? warningCode.hashCode() : 0); + result = 31 * result + (errorCode != null ? errorCode.hashCode() : 0); + result = 31 * result + (message != null ? message.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "PumpAlert{" + + "timestamp=" + timestamp + "(" + new Date(timestamp) + ")" + + ", warningCode=" + warningCode + + ", errorCode=" + errorCode + + ", message='" + message + '\'' + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/PumpHistory.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/PumpHistory.java new file mode 100644 index 0000000000..ecc8a1a29f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/PumpHistory.java @@ -0,0 +1,50 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** History data as read from the pump's My Data menu. + * Records are ordered from newest to oldest, so the first record is always the newest. */ +public class PumpHistory { + @NonNull + public List bolusHistory = new ArrayList<>(); + @NonNull + public List tbrHistory = new ArrayList<>(); + @NonNull + public List pumpAlertHistory = new LinkedList<>(); + @NonNull + public List tddHistory = new ArrayList<>(); + + public PumpHistory bolusHistory(List bolusHistory) { + this.bolusHistory = bolusHistory; + return this; + } + + public PumpHistory tbrHistory(List tbrHistory) { + this.tbrHistory = tbrHistory; + return this; + } + + public PumpHistory pumpErrorHistory(List pumpAlertHistory) { + this.pumpAlertHistory = pumpAlertHistory; + return this; + } + + public PumpHistory tddHistory(List tddHistory) { + this.tddHistory = tddHistory; + return this; + } + + @Override + public String toString() { + return "PumpHistory{" + + "bolusHistory=" + bolusHistory.size() + + ", tbrHistory=" + tbrHistory.size() + + ", pumpAlertHistory=" + pumpAlertHistory.size() + + ", tddHistory=" + tddHistory.size() + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/PumpHistoryRequest.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/PumpHistoryRequest.java new file mode 100644 index 0000000000..e785c1d67c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/PumpHistoryRequest.java @@ -0,0 +1,51 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history; + +import java.util.Date; + +/** What data a 'read history' request should return. */ +public class PumpHistoryRequest { + /* History to read: + Either the timestamp of the last known record or one of the constants to read no history + or all of it. When a timestamp is provided all newer records and records matching the + timestamp are returned. Returning all records equal to the timestamp ensures a record + with a duplicate timestamp is also detected as a new record. + */ + public static final long LAST = -2; + public static final long SKIP = -1; + public static final long FULL = 0; + + public long bolusHistory = SKIP; + public long tbrHistory = SKIP; + public long pumpErrorHistory = SKIP; + public long tddHistory = SKIP; + + public PumpHistoryRequest bolusHistory(long bolusHistory) { + this.bolusHistory = bolusHistory; + return this; + } + + public PumpHistoryRequest tbrHistory(long tbrHistory) { + this.tbrHistory = tbrHistory; + return this; + } + + public PumpHistoryRequest pumpErrorHistory(long pumpErrorHistory) { + this.pumpErrorHistory = pumpErrorHistory; + return this; + } + + public PumpHistoryRequest tddHistory(long tddHistory) { + this.tddHistory = tddHistory; + return this; + } + + @Override + public String toString() { + return "PumpHistoryRequest{" + + "bolusHistory=" + bolusHistory + (bolusHistory > 0 ? ("(" + new Date(bolusHistory) + ")") : "") + + ", tbrHistory=" + tbrHistory + (tbrHistory > 0 ? ("(" + new Date(tbrHistory) + ")") : "") + + ", pumpAlertHistory=" + pumpErrorHistory + (pumpErrorHistory > 0 ? ("(" + new Date(pumpErrorHistory) + ")") : "") + + ", tddHistory=" + tddHistory + (tddHistory > 0 ? ("(" + new Date(tddHistory) + ")") : "") + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/Tbr.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/Tbr.java new file mode 100644 index 0000000000..41ec245e89 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/Tbr.java @@ -0,0 +1,44 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history; + +import java.util.Date; + +public class Tbr extends HistoryRecord { + /** Duration in minutes */ + public final int duration; + public final int percent; + + public Tbr(long timestamp, int duration, int percent) { + super(timestamp); + this.duration = duration; + this.percent = percent; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Tbr tbr = (Tbr) o; + + if (timestamp != tbr.timestamp) return false; + if (duration != tbr.duration) return false; + return percent == tbr.percent; + } + + @Override + public int hashCode() { + int result = (int) (timestamp ^ (timestamp >>> 32)); + result = 31 * result + duration; + result = 31 * result + percent; + return result; + } + + @Override + public String toString() { + return "Tbr{" + + "timestamp=" + timestamp + "(" + new Date(timestamp) + ")" + + ", duration=" + duration + + ", percent=" + percent + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/Tdd.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/Tdd.java new file mode 100644 index 0000000000..5f799f229e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ruffyscripter/history/Tdd.java @@ -0,0 +1,42 @@ +package info.nightscout.androidaps.plugins.PumpCombo.ruffyscripter.history; + +import java.util.Date; + +/** Total daily dosage; amount of insulin delivered over a full day. */ +public class Tdd extends HistoryRecord { + public final double total; + + public Tdd(long timestamp, double total) { + super(timestamp); + this.total = total; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Tdd tdd = (Tdd) o; + + if (timestamp != tdd.timestamp) return false; + return tdd.total != total; + } + + @Override + public int hashCode() { + int result; + long temp; + result = (int) (timestamp ^ (timestamp >>> 32)); + temp = Double.doubleToLongBits(total); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public String toString() { + return "Tdd{" + + "timestamp=" + timestamp + "(" + new Date(timestamp) + ")" + + ", total=" + total + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/AbstractDanaRPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/AbstractDanaRPlugin.java index ea47303fb6..ea83e6d4d6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/AbstractDanaRPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/AbstractDanaRPlugin.java @@ -7,11 +7,9 @@ import org.json.JSONObject; import org.slf4j.Logger; import java.util.Date; -import java.util.Objects; import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.Config; -import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; @@ -19,33 +17,34 @@ import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.ConstraintsInterface; import info.nightscout.androidaps.interfaces.DanaRInterface; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.ProfileInterface; import info.nightscout.androidaps.interfaces.PumpDescription; import info.nightscout.androidaps.interfaces.PumpInterface; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.ProfileNS.NSProfilePlugin; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.RecordTypes; import info.nightscout.androidaps.plugins.PumpDanaR.services.AbstractDanaRExecutionService; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.Round; +import info.nightscout.utils.SP; /** * Created by mike on 28.01.2018. */ -public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, ConstraintsInterface, ProfileInterface { +public abstract class AbstractDanaRPlugin extends PluginBase implements PumpInterface, DanaRInterface, ConstraintsInterface, ProfileInterface { protected Logger log; - protected boolean mPluginPumpEnabled = false; - protected boolean mPluginProfileEnabled = false; - protected boolean mFragmentPumpVisible = true; - protected AbstractDanaRExecutionService sExecutionService; protected DanaRPump pump = DanaRPump.getInstance(); @@ -53,79 +52,26 @@ public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface, public PumpDescription pumpDescription = new PumpDescription(); - @Override - public String getFragmentClass() { - return DanaRFragment.class.getName(); - } - - // Plugin base interface - @Override - public int getType() { - return PluginBase.PUMP; + protected AbstractDanaRPlugin() { + super(new PluginDescription() + .mainType(PluginType.PUMP) + .fragmentClass(DanaRFragment.class.getName()) + .pluginName(R.string.danarspump) + .shortName(R.string.danarpump_shortname) + .preferencesId(R.xml.pref_danars) + ); } @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.danarpump_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - if (type == PluginBase.PROFILE) return mPluginProfileEnabled && mPluginPumpEnabled; - else if (type == PluginBase.PUMP) return mPluginPumpEnabled; - else if (type == PluginBase.CONSTRAINTS) return mPluginPumpEnabled; - return false; - } - - @Override - public boolean isVisibleInTabs(int type) { - if (type == PluginBase.PROFILE || type == PluginBase.CONSTRAINTS) return false; - else if (type == PluginBase.PUMP) return mFragmentPumpVisible; - return false; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return type == PUMP; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == PluginBase.PROFILE) - mPluginProfileEnabled = fragmentEnabled; - else if (type == PluginBase.PUMP) - mPluginPumpEnabled = fragmentEnabled; + public void onStateChange(PluginType type, State oldState, State newState) { // if pump profile was enabled need to switch to another too - if (type == PluginBase.PUMP && !fragmentEnabled && mPluginProfileEnabled) { - setFragmentEnabled(PluginBase.PROFILE, false); - setFragmentVisible(PluginBase.PROFILE, false); - NSProfilePlugin.getPlugin().setFragmentEnabled(PluginBase.PROFILE, true); - NSProfilePlugin.getPlugin().setFragmentVisible(PluginBase.PROFILE, true); + if (type == PluginType.PUMP && newState == State.ENABLED && newState == State.DISABLED && isProfileInterfaceEnabled) { + setPluginEnabled(PluginType.PROFILE, false); + NSProfilePlugin.getPlugin().setPluginEnabled(PluginType.PROFILE, true); + NSProfilePlugin.getPlugin().setFragmentVisible(PluginType.PROFILE, true); } } - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == PluginBase.PUMP) - mFragmentPumpVisible = fragmentVisible; - } - @Override public boolean isSuspended() { return pump.pumpSuspended; @@ -183,7 +129,7 @@ public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface, int basalIncrement = pump.basal48Enable ? 30 * 60 : 60 * 60; for (int h = 0; h < basalValues; h++) { Double pumpValue = pump.pumpProfiles[pump.activeProfile][h]; - Double profileValue = profile.getBasal((Integer) (h * basalIncrement)); + Double profileValue = profile.getBasalTimeFromMidnight(h * basalIncrement); if (profileValue == null) return true; if (Math.abs(pumpValue - profileValue) > getPumpDescription().basalStep) { log.debug("Diff found. Hour: " + h + " Pump: " + pumpValue + " Profile: " + profileValue); @@ -213,10 +159,9 @@ public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface, } @Override - public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, boolean enforceNew) { + public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, Profile profile, boolean enforceNew) { PumpEnactResult result = new PumpEnactResult(); - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - percent = configBuilderPlugin.applyBasalConstraints(percent); + percent = MainApp.getConstraintChecker().applyBasalPercentConstraints(new Constraint<>(percent), profile).value(); if (percent < 0) { result.isTempCancel = false; result.enacted = false; @@ -227,7 +172,8 @@ public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface, } if (percent > getPumpDescription().maxTempPercent) percent = getPumpDescription().maxTempPercent; - TemporaryBasal runningTB = MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()); + long now = System.currentTimeMillis(); + TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getRealTempBasalFromHistory(now); if (runningTB != null && runningTB.percentRate == percent && !enforceNew) { result.enacted = false; result.success = true; @@ -235,7 +181,6 @@ public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface, result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); result.duration = pump.tempBasalRemainingMin; result.percent = pump.tempBasalPercent; - result.absolute = MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory(); result.isPercent = true; if (Config.logPumpActions) log.debug("setTempBasalPercent: Correct value already set"); @@ -250,7 +195,6 @@ public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface, result.isTempCancel = false; result.duration = pump.tempBasalRemainingMin; result.percent = pump.tempBasalPercent; - result.absolute = MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory(); result.isPercent = true; if (Config.logPumpActions) log.debug("setTempBasalPercent: OK"); @@ -265,14 +209,13 @@ public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface, @Override public PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes) { - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - insulin = configBuilderPlugin.applyBolusConstraints(insulin); + insulin = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(insulin)).value(); // needs to be rounded int durationInHalfHours = Math.max(durationInMinutes / 30, 1); insulin = Round.roundTo(insulin, getPumpDescription().extendedBolusStep); PumpEnactResult result = new PumpEnactResult(); - ExtendedBolus runningEB = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); + ExtendedBolus runningEB = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(System.currentTimeMillis()); if (runningEB != null && Math.abs(runningEB.insulin - insulin) < getPumpDescription().extendedBolusStep) { result.enacted = false; result.success = true; @@ -293,7 +236,8 @@ public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface, result.isTempCancel = false; result.duration = pump.extendedBolusRemainingMinutes; result.absolute = pump.extendedBolusAbsoluteRate; - result.bolusDelivered = pump.extendedBolusAmount; + if (!SP.getBoolean("danar_useextended", false)) + result.bolusDelivered = pump.extendedBolusAmount; result.isPercent = false; if (Config.logPumpActions) log.debug("setExtendedBolus: OK"); @@ -309,7 +253,7 @@ public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface, @Override public PumpEnactResult cancelExtendedBolus() { PumpEnactResult result = new PumpEnactResult(); - ExtendedBolus runningEB = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); + ExtendedBolus runningEB = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(System.currentTimeMillis()); if (runningEB != null) { sExecutionService.extendedBolusStop(); result.enacted = true; @@ -360,11 +304,16 @@ public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface, @Override public void getPumpStatus() { - if (sExecutionService != null) sExecutionService.getPumpStatus(); + if (sExecutionService != null) { + sExecutionService.getPumpStatus(); + pumpDescription.basalStep = pump.basalStep; + pumpDescription.bolusStep = pump.bolusStep; + } } @Override - public JSONObject getJSONStatus() { + public JSONObject getJSONStatus(Profile profile, String profilename) { + long now = System.currentTimeMillis(); if (pump.lastConnection + 5 * 60 * 1000L < System.currentTimeMillis()) { return null; } @@ -382,13 +331,13 @@ public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface, extended.put("LastBolus", pump.lastBolusTime.toLocaleString()); extended.put("LastBolusAmount", pump.lastBolusAmount); } - TemporaryBasal tb = MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()); + TemporaryBasal tb = TreatmentsPlugin.getPlugin().getRealTempBasalFromHistory(now); if (tb != null) { - extended.put("TempBasalAbsoluteRate", tb.tempBasalConvertedToAbsolute(System.currentTimeMillis())); + extended.put("TempBasalAbsoluteRate", tb.tempBasalConvertedToAbsolute(now, profile)); extended.put("TempBasalStart", DateUtil.dateAndTimeString(tb.date)); extended.put("TempBasalRemaining", tb.getPlannedRemainingMinutes()); } - ExtendedBolus eb = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); + ExtendedBolus eb = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(now); if (eb != null) { extended.put("ExtendedBolusAbsoluteRate", eb.absoluteRate()); extended.put("ExtendedBolusStart", DateUtil.dateAndTimeString(eb.date)); @@ -396,8 +345,8 @@ public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface, } extended.put("BaseBasalRate", getBaseBasalRate()); try { - extended.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName()); - } catch (Exception e) { + extended.put("ActiveProfile", profilename); + } catch (Exception ignored) { } pumpjson.put("battery", battery); @@ -435,80 +384,27 @@ public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface, */ @Override - public boolean isLoopEnabled() { - return true; - } - - @Override - public boolean isClosedModeEnabled() { - return true; - } - - @Override - public boolean isAutosensModeEnabled() { - return true; - } - - @Override - public boolean isAMAModeEnabled() { - return true; - } - - @Override - public boolean isSMBModeEnabled() { - return true; - } - - @SuppressWarnings("PointlessBooleanExpression") - @Override - public Double applyBasalConstraints(Double absoluteRate) { - double origAbsoluteRate = absoluteRate; - if (pump != null) { - if (absoluteRate > pump.maxBasal) { - absoluteRate = pump.maxBasal; - if (Config.logConstraintsChanges && origAbsoluteRate != Constants.basalAbsoluteOnlyForCheckLimit) - log.debug("Limiting rate " + origAbsoluteRate + "U/h by pump constraint to " + absoluteRate + "U/h"); - } - } + public Constraint applyBasalConstraints(Constraint absoluteRate, Profile profile) { + if (pump != null) + absoluteRate.setIfSmaller(pump.maxBasal, String.format(MainApp.gs(R.string.limitingbasalratio), pump.maxBasal, MainApp.gs(R.string.pumplimit)), this); return absoluteRate; } - @SuppressWarnings("PointlessBooleanExpression") @Override - public Integer applyBasalConstraints(Integer percentRate) { - Integer origPercentRate = percentRate; - if (percentRate < 0) percentRate = 0; - if (percentRate > getPumpDescription().maxTempPercent) - percentRate = getPumpDescription().maxTempPercent; - if (!Objects.equals(percentRate, origPercentRate) && Config.logConstraintsChanges && !Objects.equals(origPercentRate, Constants.basalPercentOnlyForCheckLimit)) - log.debug("Limiting percent rate " + origPercentRate + "% to " + percentRate + "%"); + public Constraint applyBasalPercentConstraints(Constraint percentRate, Profile profile) { + percentRate.setIfGreater(0, String.format(MainApp.gs(R.string.limitingpercentrate), 0, MainApp.gs(R.string.itmustbepositivevalue)), this); + percentRate.setIfSmaller(getPumpDescription().maxTempPercent, String.format(MainApp.gs(R.string.limitingpercentrate), getPumpDescription().maxTempPercent, MainApp.gs(R.string.pumplimit)), this); + return percentRate; } - @SuppressWarnings("PointlessBooleanExpression") @Override - public Double applyBolusConstraints(Double insulin) { - double origInsulin = insulin; - if (pump != null) { - if (insulin > pump.maxBolus) { - insulin = pump.maxBolus; - if (Config.logConstraintsChanges && origInsulin != Constants.bolusOnlyForCheckLimit) - log.debug("Limiting bolus " + origInsulin + "U by pump constraint to " + insulin + "U"); - } - } + public Constraint applyBolusConstraints(Constraint insulin) { + if (pump != null) + insulin.setIfSmaller(pump.maxBolus, String.format(MainApp.gs(R.string.limitingbolus), pump.maxBolus, MainApp.gs(R.string.pumplimit)), this); return insulin; } - @Override - public Integer applyCarbsConstraints(Integer carbs) { - return carbs; - } - - @Override - public Double applyMaxIOBConstraints(Double maxIob) { - return maxIob; - } - @Nullable @Override public ProfileStore getProfile() { @@ -527,6 +423,11 @@ public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface, return pump.createConvertedProfileName(); } + @Override + public PumpEnactResult loadTDDs() { + return loadHistory(RecordTypes.RECORD_TYPE_DAILY); + } + // Reply for sms communicator public String shortStatus(boolean veryShort) { String ret = ""; @@ -538,11 +439,13 @@ public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface, if (pump.lastBolusTime.getTime() != 0) { ret += "LastBolus: " + DecimalFormatter.to2Decimal(pump.lastBolusAmount) + "U @" + android.text.format.DateFormat.format("HH:mm", pump.lastBolusTime) + "\n"; } - if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) { - ret += "Temp: " + MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()).toStringFull() + "\n"; + TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getRealTempBasalFromHistory(System.currentTimeMillis()); + if (activeTemp != null) { + ret += "Temp: " + activeTemp.toStringFull() + "\n"; } - if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) { - ret += "Extended: " + MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()).toString() + "\n"; + ExtendedBolus activeExtendedBolus = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(System.currentTimeMillis()); + if (activeExtendedBolus != null) { + ret += "Extended: " + activeExtendedBolus.toString() + "\n"; } if (!veryShort) { ret += "TDD: " + DecimalFormatter.to0Decimal(pump.dailyTotalUnits) + " / " + pump.maxDailyTotalUnits + " U\n"; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRFragment.java index 1ca54a7eb6..7ef3d6a987 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRFragment.java @@ -11,24 +11,21 @@ import android.text.Spanned; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; import com.squareup.otto.Subscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Date; - import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; -import butterknife.Unbinder; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.TDDStatsActivity; +import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.events.EventExtendedBolusChange; import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.events.EventTempBasalChange; @@ -36,11 +33,12 @@ import info.nightscout.androidaps.plugins.Common.SubscriberFragment; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.PumpDanaR.Dialogs.ProfileViewDialog; import info.nightscout.androidaps.plugins.PumpDanaR.activities.DanaRHistoryActivity; -import info.nightscout.androidaps.plugins.PumpDanaR.activities.DanaRStatsActivity; import info.nightscout.androidaps.plugins.PumpDanaR.events.EventDanaRNewStatus; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.androidaps.queue.events.EventQueueChanged; import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.SetWarnColor; public class DanaRFragment extends SubscriberFragment { @@ -55,24 +53,41 @@ public class DanaRFragment extends SubscriberFragment { } }; - @BindView(R.id.danar_lastconnection) TextView lastConnectionView; - @BindView(R.id.danar_btconnection) TextView btConnectionView; - @BindView(R.id.danar_lastbolus) TextView lastBolusView; - @BindView(R.id.danar_dailyunits) TextView dailyUnitsView; - @BindView(R.id.danar_basabasalrate) TextView basaBasalRateView; - @BindView(R.id.danar_tempbasal) TextView tempBasalView; - @BindView(R.id.danar_extendedbolus) TextView extendedBolusView; - @BindView(R.id.danar_battery) TextView batteryView; - @BindView(R.id.danar_reservoir) TextView reservoirView; - @BindView(R.id.danar_iob) TextView iobView; - @BindView(R.id.danar_firmware) TextView firmwareView; - @BindView(R.id.danar_basalstep) TextView basalStepView; - @BindView(R.id.danar_bolusstep) TextView bolusStepView; - @BindView(R.id.danar_serialnumber) TextView serialNumberView; - @BindView(R.id.danar_queue) TextView queueView; + @BindView(R.id.danar_lastconnection) + TextView lastConnectionView; + @BindView(R.id.danar_btconnection) + TextView btConnectionView; + @BindView(R.id.danar_lastbolus) + TextView lastBolusView; + @BindView(R.id.danar_dailyunits) + TextView dailyUnitsView; + @BindView(R.id.danar_basabasalrate) + TextView basaBasalRateView; + @BindView(R.id.danar_tempbasal) + TextView tempBasalView; + @BindView(R.id.danar_extendedbolus) + TextView extendedBolusView; + @BindView(R.id.danar_battery) + TextView batteryView; + @BindView(R.id.danar_reservoir) + TextView reservoirView; + @BindView(R.id.danar_iob) + TextView iobView; + @BindView(R.id.danar_firmware) + TextView firmwareView; + @BindView(R.id.danar_basalstep) + TextView basalStepView; + @BindView(R.id.danar_bolusstep) + TextView bolusStepView; + @BindView(R.id.danar_serialnumber) + TextView serialNumberView; + @BindView(R.id.danar_queue) + TextView queueView; - @BindView(R.id.overview_pumpstatuslayout) LinearLayout pumpStatusLayout; - @BindView(R.id.overview_pumpstatus) TextView pumpStatusView; + @BindView(R.id.overview_pumpstatuslayout) + LinearLayout pumpStatusLayout; + @BindView(R.id.overview_pumpstatus) + TextView pumpStatusView; public DanaRFragment() { } @@ -100,27 +115,31 @@ public class DanaRFragment extends SubscriberFragment { return view; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } return null; } - @OnClick(R.id.danar_history) void onHistoryClick() { + @OnClick(R.id.danar_history) + void onHistoryClick() { startActivity(new Intent(getContext(), DanaRHistoryActivity.class)); } - @OnClick(R.id.danar_viewprofile) void onViewProfileClick() { + @OnClick(R.id.danar_viewprofile) + void onViewProfileClick() { FragmentManager manager = getFragmentManager(); ProfileViewDialog profileViewDialog = new ProfileViewDialog(); profileViewDialog.show(manager, "ProfileViewDialog"); } - @OnClick(R.id.danar_stats) void onStatsClick() { - startActivity(new Intent(getContext(), DanaRStatsActivity.class)); + @OnClick(R.id.danar_stats) + void onStatsClick() { + startActivity(new Intent(getContext(), TDDStatsActivity.class)); } - @OnClick(R.id.danar_btconnection) void onBtConnectionClick() { + @OnClick(R.id.danar_btconnection) + void onBtConnectionClick() { log.debug("Clicked connect to pump"); DanaRPump.getInstance().lastConnection = 0; ConfigBuilderPlugin.getCommandQueue().readStatus("Clicked connect to pump", null); @@ -203,21 +222,22 @@ public class DanaRFragment extends SubscriberFragment { basaBasalRateView.setText("( " + (pump.activeProfile + 1) + " ) " + DecimalFormatter.to2Decimal(ConfigBuilderPlugin.getActivePump().getBaseBasalRate()) + " U/h"); // DanaRPlugin, DanaRKoreanPlugin if (ConfigBuilderPlugin.getActivePump().isFakingTempsByExtendedBoluses()) { - if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) { - tempBasalView.setText(MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()).toStringFull()); + if (TreatmentsPlugin.getPlugin().isInHistoryRealTempBasalInProgress()) { + tempBasalView.setText(TreatmentsPlugin.getPlugin().getRealTempBasalFromHistory(System.currentTimeMillis()).toStringFull()); } else { tempBasalView.setText(""); } } else { // v2 plugin - if (MainApp.getConfigBuilder().isTempBasalInProgress()) { - tempBasalView.setText(MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()).toStringFull()); + if (TreatmentsPlugin.getPlugin().isTempBasalInProgress()) { + tempBasalView.setText(TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()).toStringFull()); } else { tempBasalView.setText(""); } } - if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) { - extendedBolusView.setText(MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()).toString()); + ExtendedBolus activeExtendedBolus = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(System.currentTimeMillis()); + if (activeExtendedBolus != null) { + extendedBolusView.setText(activeExtendedBolus.toString()); } else { extendedBolusView.setText(""); } @@ -226,7 +246,7 @@ public class DanaRFragment extends SubscriberFragment { batteryView.setText("{fa-battery-" + (pump.batteryRemaining / 25) + "}"); SetWarnColor.setColorInverse(batteryView, pump.batteryRemaining, 51d, 26d); iobView.setText(pump.iob + " U"); - if (pump.isNewPump) { + if (pump.model != 0 || pump.protocol != 0 || pump.productCode != 0) { firmwareView.setText(String.format(MainApp.sResources.getString(R.string.danar_model), pump.model, pump.protocol, pump.productCode)); } else { firmwareView.setText("OLD"); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java index 9a0be55897..7a91437f17 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java @@ -14,14 +14,19 @@ import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.TemporaryBasal; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgBolusStartWithSpeed; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventPreferenceChange; +import info.nightscout.androidaps.interfaces.Constraint; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.PumpDescription; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.PumpDanaR.services.DanaRExecutionService; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.Round; import info.nightscout.utils.SP; @@ -39,14 +44,10 @@ public class DanaRPlugin extends AbstractDanaRPlugin { } public DanaRPlugin() { + super(); log = LoggerFactory.getLogger(DanaRPlugin.class); useExtendedBoluses = SP.getBoolean("danar_useextended", false); - Context context = MainApp.instance().getApplicationContext(); - Intent intent = new Intent(context, DanaRExecutionService.class); - context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); - MainApp.bus().register(this); - pumpDescription.isBolusCapable = true; pumpDescription.bolusStep = 0.05d; @@ -71,7 +72,27 @@ public class DanaRPlugin extends AbstractDanaRPlugin { pumpDescription.isRefillingCapable = true; - pumpDescription.storesCarbInfo = true; + pumpDescription.storesCarbInfo = false; + + pumpDescription.supportsTDDs = true; + pumpDescription.needsManualTDDLoad = true; + } + + @Override + protected void onStart() { + Context context = MainApp.instance().getApplicationContext(); + Intent intent = new Intent(context, DanaRExecutionService.class); + context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + MainApp.bus().register(this); + super.onStart(); + } + + @Override + protected void onStop() { + Context context = MainApp.instance().getApplicationContext(); + context.unbindService(mConnection); + + MainApp.bus().unregister(this); } private ServiceConnection mConnection = new ServiceConnection() { @@ -96,11 +117,11 @@ public class DanaRPlugin extends AbstractDanaRPlugin { @Subscribe public void onStatusEvent(final EventPreferenceChange s) { - if (isEnabled(PUMP)) { + if (isEnabled(PluginType.PUMP)) { boolean previousValue = useExtendedBoluses; useExtendedBoluses = SP.getBoolean("danar_useextended", false); - if (useExtendedBoluses != previousValue && MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) { + if (useExtendedBoluses != previousValue && TreatmentsPlugin.getPlugin().isInHistoryExtendedBoluslInProgress()) { sExecutionService.extendedBolusStop(); } } @@ -109,7 +130,7 @@ public class DanaRPlugin extends AbstractDanaRPlugin { // Plugin base interface @Override public String getName() { - return MainApp.instance().getString(R.string.danarpump); + return MainApp.gs(R.string.danarpump); } @Override @@ -130,29 +151,33 @@ public class DanaRPlugin extends AbstractDanaRPlugin { @Override public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - detailedBolusInfo.insulin = configBuilderPlugin.applyBolusConstraints(detailedBolusInfo.insulin); + detailedBolusInfo.insulin = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(detailedBolusInfo.insulin)).value(); if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) { Treatment t = new Treatment(); + t.isSMB = detailedBolusInfo.isSMB; boolean connectionOK = false; - if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) connectionOK = sExecutionService.bolus(detailedBolusInfo.insulin, (int) detailedBolusInfo.carbs, detailedBolusInfo.carbTime, t); + if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) + connectionOK = sExecutionService.bolus(detailedBolusInfo.insulin, (int) detailedBolusInfo.carbs, detailedBolusInfo.carbTime, t); PumpEnactResult result = new PumpEnactResult(); - result.success = connectionOK; + result.success = connectionOK && detailedBolusInfo.insulin == t.insulin; result.bolusDelivered = t.insulin; result.carbsDelivered = detailedBolusInfo.carbs; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + if (!result.success) + result.comment = String.format(MainApp.gs(R.string.boluserrorcode), detailedBolusInfo.insulin, t.insulin, MsgBolusStartWithSpeed.errorCode); + else + result.comment = MainApp.gs(R.string.virtualpump_resultok); if (Config.logPumpActions) log.debug("deliverTreatment: OK. Asked: " + detailedBolusInfo.insulin + " Delivered: " + result.bolusDelivered); detailedBolusInfo.insulin = t.insulin; detailedBolusInfo.date = System.currentTimeMillis(); - MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); + TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo); return result; } else { PumpEnactResult result = new PumpEnactResult(); result.success = false; result.bolusDelivered = 0d; result.carbsDelivered = 0d; - result.comment = MainApp.instance().getString(R.string.danar_invalidinput); + result.comment = MainApp.gs(R.string.danar_invalidinput); log.error("deliverTreatment: Invalid input"); return result; } @@ -160,7 +185,7 @@ public class DanaRPlugin extends AbstractDanaRPlugin { // This is called from APS @Override - public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean enforceNew) { + public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, Profile profile, boolean enforceNew) { // Recheck pump status if older than 30 min //This should not be needed while using queue because connection should be done before calling this //if (pump.lastConnection.getTime() + 30 * 60 * 1000L < System.currentTimeMillis()) { @@ -169,23 +194,26 @@ public class DanaRPlugin extends AbstractDanaRPlugin { PumpEnactResult result = new PumpEnactResult(); - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - absoluteRate = configBuilderPlugin.applyBasalConstraints(absoluteRate); + absoluteRate = MainApp.getConstraintChecker().applyBasalConstraints(new Constraint<>(absoluteRate), profile).value(); final boolean doTempOff = getBaseBasalRate() - absoluteRate == 0d; final boolean doLowTemp = absoluteRate < getBaseBasalRate(); final boolean doHighTemp = absoluteRate > getBaseBasalRate() && !useExtendedBoluses; final boolean doExtendedTemp = absoluteRate > getBaseBasalRate() && useExtendedBoluses; + long now = System.currentTimeMillis(); + TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getRealTempBasalFromHistory(now); + ExtendedBolus activeExtended = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(now); + if (doTempOff) { // If extended in progress - if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress() && useExtendedBoluses) { + if (activeExtended != null && useExtendedBoluses) { if (Config.logPumpActions) log.debug("setTempBasalAbsolute: Stopping extended bolus (doTempOff)"); return cancelExtendedBolus(); } // If temp in progress - if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) { + if (activeTemp != null) { if (Config.logPumpActions) log.debug("setTempBasalAbsolute: Stopping temp basal (doTempOff)"); return cancelRealTempBasal(); @@ -211,7 +239,7 @@ public class DanaRPlugin extends AbstractDanaRPlugin { log.debug("setTempBasalAbsolute: Calculated percent rate: " + percentRate); // If extended in progress - if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress() && useExtendedBoluses) { + if (activeExtended != null && useExtendedBoluses) { if (Config.logPumpActions) log.debug("setTempBasalAbsolute: Stopping extended bolus (doLowTemp || doHighTemp)"); result = cancelExtendedBolus(); @@ -221,20 +249,18 @@ public class DanaRPlugin extends AbstractDanaRPlugin { } } // Check if some temp is already in progress - if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) { + if (activeTemp != null) { // Correct basal already set ? - TemporaryBasal running = MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()); if (Config.logPumpActions) - log.debug("setTempBasalAbsolute: currently running: " + running.toString()); - if (running.percentRate == percentRate) { + log.debug("setTempBasalAbsolute: currently running: " + activeTemp.toString()); + if (activeTemp.percentRate == percentRate) { if (enforceNew) { cancelTempBasal(true); } else { result.success = true; result.percent = percentRate; - result.absolute = MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory(); result.enacted = false; - result.duration = ((Double) MainApp.getConfigBuilder().getTempBasalRemainingMinutesFromHistory()).intValue(); + result.duration = activeTemp.getPlannedRemainingMinutes(); result.isPercent = true; result.isTempCancel = false; if (Config.logPumpActions) @@ -246,11 +272,11 @@ public class DanaRPlugin extends AbstractDanaRPlugin { // Convert duration from minutes to hours if (Config.logPumpActions) log.debug("setTempBasalAbsolute: Setting temp basal " + percentRate + "% for " + durationInMinutes + " mins (doLowTemp || doHighTemp)"); - return setTempBasalPercent(percentRate, durationInMinutes, false); + return setTempBasalPercent(percentRate, durationInMinutes, profile, false); } if (doExtendedTemp) { // Check if some temp is already in progress - if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) { + if (activeTemp != null) { if (Config.logPumpActions) log.debug("setTempBasalAbsolute: Stopping temp basal (doExtendedTemp)"); result = cancelRealTempBasal(); @@ -265,18 +291,18 @@ public class DanaRPlugin extends AbstractDanaRPlugin { Integer durationInHalfHours = Math.max(durationInMinutes / 30, 1); // We keep current basal running so need to sub current basal Double extendedRateToSet = absoluteRate - getBaseBasalRate(); - extendedRateToSet = configBuilderPlugin.applyBasalConstraints(extendedRateToSet); + extendedRateToSet = MainApp.getConstraintChecker().applyBasalConstraints(new Constraint<>(extendedRateToSet), profile).value(); // needs to be rounded to 0.1 extendedRateToSet = Round.roundTo(extendedRateToSet, pumpDescription.extendedBolusStep * 2); // *2 because of halfhours // What is current rate of extended bolusing in u/h? if (Config.logPumpActions) { - log.debug("setTempBasalAbsolute: Extended bolus in progress: " + MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress() + " rate: " + pump.extendedBolusAbsoluteRate + "U/h duration remaining: " + pump.extendedBolusRemainingMinutes + "min"); + log.debug("setTempBasalAbsolute: Extended bolus in progress: " + (activeExtended != null) + " rate: " + pump.extendedBolusAbsoluteRate + "U/h duration remaining: " + pump.extendedBolusRemainingMinutes + "min"); log.debug("setTempBasalAbsolute: Rate to set: " + extendedRateToSet + "U/h"); } // Compare with extended rate in progress - if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress() && Math.abs(pump.extendedBolusAbsoluteRate - extendedRateToSet) < getPumpDescription().extendedBolusStep) { + if (activeExtended != null && Math.abs(pump.extendedBolusAbsoluteRate - extendedRateToSet) < getPumpDescription().extendedBolusStep) { // correct extended already set result.success = true; result.absolute = pump.extendedBolusAbsoluteRate; @@ -312,23 +338,22 @@ public class DanaRPlugin extends AbstractDanaRPlugin { @Override public PumpEnactResult cancelTempBasal(boolean force) { - if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) + if (TreatmentsPlugin.getPlugin().isInHistoryRealTempBasalInProgress()) return cancelRealTempBasal(); - if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress() && useExtendedBoluses) { - PumpEnactResult cancelEx = cancelExtendedBolus(); - return cancelEx; + if (TreatmentsPlugin.getPlugin().isInHistoryExtendedBoluslInProgress() && useExtendedBoluses) { + return cancelExtendedBolus(); } PumpEnactResult result = new PumpEnactResult(); result.success = true; result.enacted = false; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.comment = MainApp.gs(R.string.virtualpump_resultok); result.isTempCancel = true; return result; } public PumpEnactResult cancelRealTempBasal() { PumpEnactResult result = new PumpEnactResult(); - TemporaryBasal runningTB = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); + TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); if (runningTB != null) { sExecutionService.tempBasalStop(); result.enacted = true; @@ -337,13 +362,13 @@ public class DanaRPlugin extends AbstractDanaRPlugin { if (!pump.isTempBasalInProgress) { result.success = true; result.isTempCancel = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.comment = MainApp.gs(R.string.virtualpump_resultok); if (Config.logPumpActions) log.debug("cancelRealTempBasal: OK"); return result; } else { result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_valuenotsetproperly); + result.comment = MainApp.gs(R.string.danar_valuenotsetproperly); result.isTempCancel = true; log.error("cancelRealTempBasal: Failed to cancel temp basal"); return result; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPump.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPump.java index 0e1a9ceb75..1e346695e3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPump.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPump.java @@ -29,6 +29,10 @@ public class DanaRPump { return instance; } + public static void reset() { + instance = null; + } + public static final int UNITS_MGDL = 0; public static final int UNITS_MMOL = 1; @@ -69,9 +73,9 @@ public class DanaRPump { public static final int DOMESTIC_MODEL = 0x01; public static final int EXPORT_MODEL = 0x03; - public int model; - public int protocol; - public int productCode; + public int model = 0; + public int protocol = 0; + public int productCode = 0; public boolean isConfigUD; public boolean isExtendedBolusEnabled; @@ -237,7 +241,7 @@ public class DanaRPump { for (Integer hour = 0; hour < 24; hour++) { //Some values get truncated to the next lower one. // -> round them to two decimals and make sure we are a small delta larger (that will get truncated) - double value = Math.round(100d * nsProfile.getBasal((Integer) (hour * 60 * 60)))/100d + 0.00001; + double value = Math.round(100d * nsProfile.getBasalTimeFromMidnight((Integer) (hour * 60 * 60)))/100d + 0.00001; if (Config.logDanaMessageDetail) log.debug("NS basal value for " + hour + ":00 is " + value); record[hour] = value; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/Dialogs/ProfileViewDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/Dialogs/ProfileViewDialog.java index 5ae732c66a..d8157281bd 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/Dialogs/ProfileViewDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/Dialogs/ProfileViewDialog.java @@ -26,6 +26,7 @@ import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPlugin; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; import info.nightscout.androidaps.plugins.PumpDanaRKorean.DanaRKoreanPlugin; import info.nightscout.androidaps.plugins.PumpDanaRv2.DanaRv2Plugin; +import info.nightscout.androidaps.plugins.Treatments.fragments.ProfileGraph; import info.nightscout.utils.DecimalFormatter; /** @@ -42,6 +43,8 @@ public class ProfileViewDialog extends DialogFragment { private TextView isf; private TextView basal; private TextView target; + private ProfileGraph basalGraph; + private Button refreshButton; @@ -70,7 +73,7 @@ public class ProfileViewDialog extends DialogFragment { dismiss(); } }); - + basalGraph = (ProfileGraph) layout.findViewById(R.id.basal_graph); setContent(); return layout; } @@ -82,12 +85,6 @@ public class ProfileViewDialog extends DialogFragment { } private void setContent() { -// if (profile == null) { -// noProfile.setVisibility(View.VISIBLE); -// return; -// } else { -// noProfile.setVisibility(View.GONE); -// } ProfileStore store = ((ProfileInterface)MainApp.getConfigBuilder().getActivePump()).getProfile(); if (store != null) { noProfile.setVisibility(View.GONE); @@ -99,6 +96,7 @@ public class ProfileViewDialog extends DialogFragment { isf.setText(profile.getIsfList()); basal.setText(profile.getBasalList()); target.setText(profile.getTargetList()); + basalGraph.show(store.getDefaultProfile()); } else { noProfile.setVisibility(View.VISIBLE); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/activities/DanaRHistoryActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/activities/DanaRHistoryActivity.java index 583a0b68c2..7f90fb190b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/activities/DanaRHistoryActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/activities/DanaRHistoryActivity.java @@ -1,14 +1,9 @@ package info.nightscout.androidaps.plugins.PumpDanaR.activities; import android.app.Activity; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; -import android.os.IBinder; import android.support.v7.widget.CardView; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -32,14 +27,11 @@ import java.util.List; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.DanaRHistoryRecord; import info.nightscout.androidaps.events.EventPumpStatusChanged; -import info.nightscout.androidaps.data.Profile; -import info.nightscout.androidaps.interfaces.DanaRInterface; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.PumpDanaR.services.DanaRExecutionService; import info.nightscout.androidaps.plugins.PumpDanaR.comm.RecordTypes; import info.nightscout.androidaps.plugins.PumpDanaR.events.EventDanaRSyncStatus; import info.nightscout.androidaps.plugins.PumpDanaRKorean.DanaRKoreanPlugin; @@ -124,8 +116,8 @@ public class DanaRHistoryActivity extends Activity { statusView.setVisibility(View.GONE); - boolean isKorean = MainApp.getSpecificPlugin(DanaRKoreanPlugin.class) != null && MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).isEnabled(PluginBase.PUMP); - boolean isRS = MainApp.getSpecificPlugin(DanaRSPlugin.class) != null && MainApp.getSpecificPlugin(DanaRSPlugin.class).isEnabled(PluginBase.PUMP); + boolean isKorean = MainApp.getSpecificPlugin(DanaRKoreanPlugin.class) != null && MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).isEnabled(PluginType.PUMP); + boolean isRS = MainApp.getSpecificPlugin(DanaRSPlugin.class) != null && MainApp.getSpecificPlugin(DanaRSPlugin.class).isEnabled(PluginType.PUMP); // Types diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MessageBase.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MessageBase.java index 943d4ee502..2e6c02458e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MessageBase.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MessageBase.java @@ -23,7 +23,7 @@ import info.nightscout.utils.CRC; public class MessageBase { private static Logger log = LoggerFactory.getLogger(MessageBase.class); - private byte[] buffer = new byte[512]; + protected byte[] buffer = new byte[512]; private int position = 6; public boolean received = false; @@ -34,6 +34,10 @@ public class MessageBase { this.buffer[5] = (byte) (cmd & 0xFF); } + public void resetBuffer() { + position = 6; + } + public void AddParamByte(byte data) { this.buffer[this.position++] = data; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgBolusProgress.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgBolusProgress.java index b200ccc5c1..79358526fa 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgBolusProgress.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgBolusProgress.java @@ -1,16 +1,12 @@ package info.nightscout.androidaps.plugins.PumpDanaR.comm; -import com.squareup.otto.Bus; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Date; - import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress; public class MsgBolusProgress extends MessageBase { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgBolusStart.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgBolusStart.java index 0e6a3e814a..7586edf825 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgBolusStart.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgBolusStart.java @@ -3,14 +3,16 @@ package info.nightscout.androidaps.plugins.PumpDanaR.comm; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.utils.HardLimits; public class MsgBolusStart extends MessageBase { private static Logger log = LoggerFactory.getLogger(MsgBolusStart.class); + public static int errorCode; + public MsgBolusStart() { SetCommand(0x0102); } @@ -19,9 +21,7 @@ public class MsgBolusStart extends MessageBase { this(); // HARDCODED LIMIT - amount = MainApp.getConfigBuilder().applyBolusConstraints(amount); - if (amount < 0) amount = 0d; - if (amount > HardLimits.maxBolus()) amount = HardLimits.maxBolus(); + amount = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(amount)).value(); AddParamInt((int) (amount * 100)); @@ -31,13 +31,13 @@ public class MsgBolusStart extends MessageBase { @Override public void handleMessage(byte[] bytes) { - int result = intFromBuff(bytes, 0, 1); - if (result != 2) { + errorCode = intFromBuff(bytes, 0, 1); + if (errorCode != 2) { failed = true; - log.debug("Messsage response: " + result + " FAILED!!"); + log.debug("Messsage response: " + errorCode + " FAILED!!"); } else { if (Config.logDanaMessageDetail) - log.debug("Messsage response: " + result); + log.debug("Messsage response: " + errorCode + " OK"); } } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgBolusStartWithSpeed.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgBolusStartWithSpeed.java index b04d501ef0..dd4251a350 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgBolusStartWithSpeed.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgBolusStartWithSpeed.java @@ -5,11 +5,14 @@ import org.slf4j.LoggerFactory; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.utils.HardLimits; public class MsgBolusStartWithSpeed extends MessageBase { private static Logger log = LoggerFactory.getLogger(MsgBolusStartWithSpeed.class); + public static int errorCode; + public MsgBolusStartWithSpeed() { SetCommand(0x0104); } @@ -18,9 +21,7 @@ public class MsgBolusStartWithSpeed extends MessageBase { this(); // HARDCODED LIMIT - amount = MainApp.getConfigBuilder().applyBolusConstraints(amount); - if (amount < 0) amount = 0d; - if (amount > HardLimits.maxBolus()) amount = HardLimits.maxBolus(); + amount = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(amount)).value(); AddParamInt((int) (amount * 100)); AddParamByte((byte) speed); @@ -31,13 +32,13 @@ public class MsgBolusStartWithSpeed extends MessageBase { @Override public void handleMessage(byte[] bytes) { - int result = intFromBuff(bytes, 0, 1); - if (result != 2) { + errorCode = intFromBuff(bytes, 0, 1); + if (errorCode != 2) { failed = true; - log.debug("Messsage response: " + result + " FAILED!!"); + log.debug("Messsage response: " + errorCode + " FAILED!!"); } else { if (Config.logDanaMessageDetail) - log.debug("Messsage response: " + result); + log.debug("Messsage response: " + errorCode + " OK"); } } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgBolusStop.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgBolusStop.java index 7040b84183..6718d76194 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgBolusStop.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgBolusStop.java @@ -1,13 +1,11 @@ package info.nightscout.androidaps.plugins.PumpDanaR.comm; -import com.squareup.otto.Bus; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress; public class MsgBolusStop extends MessageBase { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgInitConnStatusTime.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgInitConnStatusTime.java index fe0d280113..642f539f6c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgInitConnStatusTime.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgInitConnStatusTime.java @@ -9,10 +9,10 @@ import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.events.EventRefreshGui; -import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPlugin; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; import info.nightscout.androidaps.plugins.PumpDanaRKorean.DanaRKoreanPlugin; @@ -31,19 +31,20 @@ public class MsgInitConnStatusTime extends MessageBase { MainApp.bus().post(new EventNewNotification(notification)); MainApp.getSpecificPlugin(DanaRPlugin.class).disconnect("Wrong Model"); log.debug("Wrong model selected. Switching to Korean DanaR"); - MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setFragmentEnabled(PluginBase.PUMP, true); - MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setFragmentVisible(PluginBase.PUMP, true); - MainApp.getSpecificPlugin(DanaRPlugin.class).setFragmentEnabled(PluginBase.PUMP, false); - MainApp.getSpecificPlugin(DanaRPlugin.class).setFragmentVisible(PluginBase.PUMP, false); + MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setPluginEnabled(PluginType.PUMP, true); + MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setFragmentVisible(PluginType.PUMP, true); + MainApp.getSpecificPlugin(DanaRPlugin.class).setPluginEnabled(PluginType.PUMP, false); + MainApp.getSpecificPlugin(DanaRPlugin.class).setFragmentVisible(PluginType.PUMP, false); + + DanaRPump.reset(); // mark not initialized - DanaRPump.getInstance().lastConnection = 0; // mark not initialized //If profile coming from pump, switch it as well - if(MainApp.getSpecificPlugin(DanaRPlugin.class).isEnabled(PluginBase.PROFILE)){ - (MainApp.getSpecificPlugin(DanaRPlugin.class)).setFragmentEnabled(PluginBase.PROFILE, false); - (MainApp.getSpecificPlugin(DanaRKoreanPlugin.class)).setFragmentEnabled(PluginBase.PROFILE, true); + if(MainApp.getSpecificPlugin(DanaRPlugin.class).isEnabled(PluginType.PROFILE)){ + (MainApp.getSpecificPlugin(DanaRPlugin.class)).setPluginEnabled(PluginType.PROFILE, false); + (MainApp.getSpecificPlugin(DanaRKoreanPlugin.class)).setPluginEnabled(PluginType.PROFILE, true); } - MainApp.getConfigBuilder().storeSettings(); + MainApp.getConfigBuilder().storeSettings("ChangingDanaDriver"); MainApp.bus().post(new EventRefreshGui()); ConfigBuilderPlugin.getCommandQueue().readStatus("PumpDriverChange", null); // force new connection return; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgSetExtendedBolusStart.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgSetExtendedBolusStart.java index 484078825e..420f0ccbf9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgSetExtendedBolusStart.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgSetExtendedBolusStart.java @@ -3,9 +3,9 @@ package info.nightscout.androidaps.plugins.PumpDanaR.comm; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.utils.HardLimits; public class MsgSetExtendedBolusStart extends MessageBase { @@ -21,9 +21,7 @@ public class MsgSetExtendedBolusStart extends MessageBase { // HARDCODED LIMITS if (halfhours < 1) halfhours = 1; if (halfhours > 16) halfhours = 16; - amount = MainApp.getConfigBuilder().applyBolusConstraints(amount); - if (amount < 0d) amount = 0d; - if (amount > HardLimits.maxBolus()) amount = HardLimits.maxBolus(); + amount = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(amount)).value(); AddParamInt((int) (amount * 100)); AddParamByte(halfhours); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgSettingMeal.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgSettingMeal.java index 5d7b4b65ac..6f87803868 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgSettingMeal.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgSettingMeal.java @@ -6,7 +6,7 @@ import org.slf4j.LoggerFactory; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.Overview.notifications.Notification; @@ -43,7 +43,7 @@ public class MsgSettingMeal extends MessageBase { } // DanaRKorean is not possible to set to 0.01 but it works when controlled from AAPS - if (DanaRKoreanPlugin.getPlugin().isEnabled(PluginBase.PUMP)) { + if (DanaRKoreanPlugin.getPlugin().isEnabled(PluginType.PUMP)) { pump.basalStep = 0.01d; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgStatusBolusExtended.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgStatusBolusExtended.java index 256ccc18da..048cc35e6a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgStatusBolusExtended.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgStatusBolusExtended.java @@ -8,11 +8,11 @@ import org.slf4j.LoggerFactory; import java.util.Date; import info.nightscout.androidaps.Config; -import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.interfaces.TreatmentsInterface; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; public class MsgStatusBolusExtended extends MessageBase { private static Logger log = LoggerFactory.getLogger(MsgStatusBolusExtended.class); @@ -65,12 +65,12 @@ public class MsgStatusBolusExtended extends MessageBase { } public static void updateExtendedBolusInDB() { - TreatmentsInterface treatmentsInterface = MainApp.getConfigBuilder(); + TreatmentsInterface treatmentsInterface = TreatmentsPlugin.getPlugin(); DanaRPump pump = DanaRPump.getInstance(); long now = System.currentTimeMillis(); - if (treatmentsInterface.isInHistoryExtendedBoluslInProgress()) { - ExtendedBolus extendedBolus = treatmentsInterface.getExtendedBolusFromHistory(System.currentTimeMillis()); + ExtendedBolus extendedBolus = treatmentsInterface.getExtendedBolusFromHistory(System.currentTimeMillis()); + if (extendedBolus != null) { if (pump.isExtendedInProgress) { if (extendedBolus.absoluteRate() != pump.extendedBolusAbsoluteRate) { // Close current extended diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgStatusTempBasal.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgStatusTempBasal.java index 725bed1220..48e88897e6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgStatusTempBasal.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgStatusTempBasal.java @@ -8,11 +8,10 @@ import org.slf4j.LoggerFactory; import java.util.Date; import info.nightscout.androidaps.Config; -import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.db.TemporaryBasal; -import info.nightscout.androidaps.interfaces.TreatmentsInterface; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; public class MsgStatusTempBasal extends MessageBase { private static Logger log = LoggerFactory.getLogger(MsgStatusTempBasal.class); @@ -59,43 +58,38 @@ public class MsgStatusTempBasal extends MessageBase { } public static void updateTempBasalInDB() { - TreatmentsInterface treatmentsInterface = MainApp.getConfigBuilder(); DanaRPump danaRPump = DanaRPump.getInstance(); long now = System.currentTimeMillis(); - if (treatmentsInterface.isInHistoryRealTempBasalInProgress()) { - TemporaryBasal tempBasal = treatmentsInterface.getRealTempBasalFromHistory(System.currentTimeMillis()); + if (TreatmentsPlugin.getPlugin().isInHistoryRealTempBasalInProgress()) { + TemporaryBasal tempBasal = TreatmentsPlugin.getPlugin().getRealTempBasalFromHistory(System.currentTimeMillis()); if (danaRPump.isTempBasalInProgress) { if (tempBasal.percentRate != danaRPump.tempBasalPercent) { // Close current temp basal - TemporaryBasal tempStop = new TemporaryBasal(danaRPump.tempBasalStart.getTime() - 1000); - tempStop.source = Source.USER; - treatmentsInterface.addToHistoryTempBasal(tempStop); + TemporaryBasal tempStop = new TemporaryBasal().date(danaRPump.tempBasalStart.getTime() - 1000).source(Source.USER); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempStop); // Create new - TemporaryBasal newTempBasal = new TemporaryBasal(); - newTempBasal.date = danaRPump.tempBasalStart.getTime(); - newTempBasal.percentRate = danaRPump.tempBasalPercent; - newTempBasal.isAbsolute = false; - newTempBasal.durationInMinutes = danaRPump.tempBasalTotalSec / 60; - newTempBasal.source = Source.USER; - treatmentsInterface.addToHistoryTempBasal(newTempBasal); + TemporaryBasal newTempBasal = new TemporaryBasal() + .date(danaRPump.tempBasalStart.getTime()) + .percent(danaRPump.tempBasalPercent) + .duration(danaRPump.tempBasalTotalSec / 60) + .source(Source.USER); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(newTempBasal); } } else { // Close current temp basal - TemporaryBasal tempStop = new TemporaryBasal(now); - tempStop.source = Source.USER; - treatmentsInterface.addToHistoryTempBasal(tempStop); + TemporaryBasal tempStop = new TemporaryBasal().date(now).source(Source.USER); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempStop); } } else { if (danaRPump.isTempBasalInProgress) { // Create new - TemporaryBasal newTempBasal = new TemporaryBasal(); - newTempBasal.date = danaRPump.tempBasalStart.getTime(); - newTempBasal.percentRate = danaRPump.tempBasalPercent; - newTempBasal.isAbsolute = false; - newTempBasal.durationInMinutes = danaRPump.tempBasalTotalSec / 60; - newTempBasal.source = Source.USER; - treatmentsInterface.addToHistoryTempBasal(newTempBasal); + TemporaryBasal newTempBasal = new TemporaryBasal() + .date(danaRPump.tempBasalStart.getTime()) + .percent(danaRPump.tempBasalPercent) + .duration(danaRPump.tempBasalTotalSec / 60) + .source(Source.USER); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(newTempBasal); } } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/AbstractDanaRExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/AbstractDanaRExecutionService.java index d853c9abf1..5524cf5c5c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/AbstractDanaRExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/AbstractDanaRExecutionService.java @@ -21,10 +21,9 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; -import info.nightscout.androidaps.plugins.PumpDanaR.SerialIOThread; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MessageBase; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgBolusStop; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryAlarm; @@ -78,6 +77,8 @@ public abstract class AbstractDanaRExecutionService extends Service { public abstract boolean highTempBasal(int percent); // Rv2 only + public abstract boolean tempBasalShortDuration(int percent, int durationInMinutes); // Rv2 only + public abstract boolean tempBasal(int percent, int durationInHours); public abstract boolean tempBasalStop(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/DanaRExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/DanaRExecutionService.java index 63215464a8..944931569b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/DanaRExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/DanaRExecutionService.java @@ -18,7 +18,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventInitializationChanged; import info.nightscout.androidaps.events.EventPreferenceChange; @@ -264,82 +264,84 @@ public class DanaRExecutionService extends AbstractDanaRExecutionService{ mSerialIOThread.sendMessage(new MsgSetCarbsEntry(carbtime, carbs)); } - MsgBolusProgress progress = new MsgBolusProgress(amount, t); // initialize static variables - long bolusStart = System.currentTimeMillis(); + if (amount > 0) { + MsgBolusProgress progress = new MsgBolusProgress(amount, t); // initialize static variables + long bolusStart = System.currentTimeMillis(); - if (!stop.stopped) { - mSerialIOThread.sendMessage(start); - } else { - t.insulin = 0d; - return false; - } - while (!stop.stopped && !start.failed) { - SystemClock.sleep(100); - if ((System.currentTimeMillis() - progress.lastReceive) > 15 * 1000L) { // if i didn't receive status for more than 15 sec expecting broken comm - stop.stopped = true; - stop.forced = true; - log.debug("Communication stopped"); + if (!stop.stopped) { + mSerialIOThread.sendMessage(start); + } else { + t.insulin = 0d; + return false; } - } - SystemClock.sleep(300); - - EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); - bolusingEvent.t = t; - bolusingEvent.percent = 99; - - mBolusingTreatment = null; - - int speed = 12; - switch (preferencesSpeed) { - case 0: - speed = 12; - break; - case 1: - speed = 30; - break; - case 2: - speed = 60; - break; - } - // try to find real amount if bolusing was interrupted or comm failed - if (t.insulin != amount) { - disconnect("bolusingInterrupted"); - long bolusDurationInMSec = (long) (amount * speed * 1000); - long expectedEnd = bolusStart + bolusDurationInMSec + 3000; - - while (System.currentTimeMillis() < expectedEnd) { - long waitTime = expectedEnd - System.currentTimeMillis(); - bolusingEvent.status = String.format(MainApp.sResources.getString(R.string.waitingforestimatedbolusend), waitTime / 1000); - MainApp.bus().post(bolusingEvent); - SystemClock.sleep(1000); - } - - final Object o = new Object(); - synchronized(o) { - ConfigBuilderPlugin.getCommandQueue().independentConnect("bolusingInterrupted", new Callback() { - @Override - public void run() { - if (mDanaRPump.lastBolusTime.getTime() > System.currentTimeMillis() - 60 * 1000L) { // last bolus max 1 min old - t.insulin = mDanaRPump.lastBolusAmount; - log.debug("Used bolus amount from history: " + mDanaRPump.lastBolusAmount); - } else { - log.debug("Bolus amount in history too old: " + mDanaRPump.lastBolusTime.toLocaleString()); - } - synchronized (o) { - o.notify(); - } - } - }); - try { - o.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); + while (!stop.stopped && !start.failed) { + SystemClock.sleep(100); + if ((System.currentTimeMillis() - progress.lastReceive) > 15 * 1000L) { // if i didn't receive status for more than 15 sec expecting broken comm + stop.stopped = true; + stop.forced = true; + log.debug("Communication stopped"); } } - } else { - ConfigBuilderPlugin.getCommandQueue().readStatus("bolusOK", null); + SystemClock.sleep(300); + + EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); + bolusingEvent.t = t; + bolusingEvent.percent = 99; + + mBolusingTreatment = null; + + int speed = 12; + switch (preferencesSpeed) { + case 0: + speed = 12; + break; + case 1: + speed = 30; + break; + case 2: + speed = 60; + break; + } + // try to find real amount if bolusing was interrupted or comm failed + if (t.insulin != amount) { + disconnect("bolusingInterrupted"); + long bolusDurationInMSec = (long) (amount * speed * 1000); + long expectedEnd = bolusStart + bolusDurationInMSec + 3000; + + while (System.currentTimeMillis() < expectedEnd) { + long waitTime = expectedEnd - System.currentTimeMillis(); + bolusingEvent.status = String.format(MainApp.sResources.getString(R.string.waitingforestimatedbolusend), waitTime / 1000); + MainApp.bus().post(bolusingEvent); + SystemClock.sleep(1000); + } + + final Object o = new Object(); + synchronized (o) { + ConfigBuilderPlugin.getCommandQueue().independentConnect("bolusingInterrupted", new Callback() { + @Override + public void run() { + if (mDanaRPump.lastBolusTime.getTime() > System.currentTimeMillis() - 60 * 1000L) { // last bolus max 1 min old + t.insulin = mDanaRPump.lastBolusAmount; + log.debug("Used bolus amount from history: " + mDanaRPump.lastBolusAmount); + } else { + log.debug("Bolus amount in history too old: " + mDanaRPump.lastBolusTime.toLocaleString()); + } + synchronized (o) { + o.notify(); + } + } + }); + try { + o.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } else { + ConfigBuilderPlugin.getCommandQueue().readStatus("bolusOK", null); + } } - return true; + return !start.failed; } public boolean carbsEntry(int amount) { @@ -354,6 +356,11 @@ public class DanaRExecutionService extends AbstractDanaRExecutionService{ return false; } + @Override + public boolean tempBasalShortDuration(int percent, int durationInMinutes) { + return false; + } + public boolean updateBasalsInPump(final Profile profile) { if (!isConnected()) return false; MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.updatingbasalrates))); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/DanaRKoreanPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/DanaRKoreanPlugin.java index 58392e65f9..03d60ad605 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/DanaRKoreanPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/DanaRKoreanPlugin.java @@ -14,15 +14,20 @@ import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.TemporaryBasal; -import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventPreferenceChange; +import info.nightscout.androidaps.interfaces.Constraint; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.PumpDescription; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.PumpDanaR.AbstractDanaRPlugin; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgBolusStart; import info.nightscout.androidaps.plugins.PumpDanaRKorean.services.DanaRKoreanExecutionService; +import info.nightscout.androidaps.plugins.Treatments.Treatment; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.Round; import info.nightscout.utils.SP; @@ -40,14 +45,10 @@ public class DanaRKoreanPlugin extends AbstractDanaRPlugin { } public DanaRKoreanPlugin() { + super(); log = LoggerFactory.getLogger(DanaRKoreanPlugin.class); useExtendedBoluses = SP.getBoolean("danar_useextended", false); - Context context = MainApp.instance().getApplicationContext(); - Intent intent = new Intent(context, DanaRKoreanExecutionService.class); - context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); - MainApp.bus().register(this); - pumpDescription.isBolusCapable = true; pumpDescription.bolusStep = 0.1d; @@ -72,7 +73,27 @@ public class DanaRKoreanPlugin extends AbstractDanaRPlugin { pumpDescription.isRefillingCapable = true; - pumpDescription.storesCarbInfo = true; + pumpDescription.storesCarbInfo = false; + + pumpDescription.supportsTDDs = true; + pumpDescription.needsManualTDDLoad = true; + } + + @Override + protected void onStart() { + Context context = MainApp.instance().getApplicationContext(); + Intent intent = new Intent(context, DanaRKoreanExecutionService.class); + context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + MainApp.bus().register(this); + super.onStart(); + } + + @Override + protected void onStop() { + Context context = MainApp.instance().getApplicationContext(); + context.unbindService(mConnection); + + MainApp.bus().unregister(this); } private ServiceConnection mConnection = new ServiceConnection() { @@ -97,11 +118,11 @@ public class DanaRKoreanPlugin extends AbstractDanaRPlugin { @Subscribe public void onStatusEvent(final EventPreferenceChange s) { - if (isEnabled(PUMP)) { + if (isEnabled(PluginType.PUMP)) { boolean previousValue = useExtendedBoluses; useExtendedBoluses = SP.getBoolean("danar_useextended", false); - if (useExtendedBoluses != previousValue && MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) { + if (useExtendedBoluses != previousValue && TreatmentsPlugin.getPlugin().isInHistoryExtendedBoluslInProgress()) { sExecutionService.extendedBolusStop(); } } @@ -110,7 +131,7 @@ public class DanaRKoreanPlugin extends AbstractDanaRPlugin { // Plugin base interface @Override public String getName() { - return MainApp.instance().getString(R.string.danarkoreanpump); + return MainApp.gs(R.string.danarkoreanpump); } @Override @@ -131,29 +152,33 @@ public class DanaRKoreanPlugin extends AbstractDanaRPlugin { @Override public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - detailedBolusInfo.insulin = configBuilderPlugin.applyBolusConstraints(detailedBolusInfo.insulin); + detailedBolusInfo.insulin = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(detailedBolusInfo.insulin)).value(); if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) { Treatment t = new Treatment(); + t.isSMB = detailedBolusInfo.isSMB; boolean connectionOK = false; - if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) connectionOK = sExecutionService.bolus(detailedBolusInfo.insulin, (int) detailedBolusInfo.carbs, detailedBolusInfo.carbTime, t); + if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) + connectionOK = sExecutionService.bolus(detailedBolusInfo.insulin, (int) detailedBolusInfo.carbs, detailedBolusInfo.carbTime, t); PumpEnactResult result = new PumpEnactResult(); - result.success = connectionOK; + result.success = connectionOK && detailedBolusInfo.insulin == t.insulin; result.bolusDelivered = t.insulin; result.carbsDelivered = detailedBolusInfo.carbs; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + if (!result.success) + result.comment = String.format(MainApp.gs(R.string.boluserrorcode), detailedBolusInfo.insulin, t.insulin, MsgBolusStart.errorCode); + else + result.comment = MainApp.gs(R.string.virtualpump_resultok); if (Config.logPumpActions) log.debug("deliverTreatment: OK. Asked: " + detailedBolusInfo.insulin + " Delivered: " + result.bolusDelivered); detailedBolusInfo.insulin = t.insulin; detailedBolusInfo.date = System.currentTimeMillis(); - MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); + TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo); return result; } else { PumpEnactResult result = new PumpEnactResult(); result.success = false; result.bolusDelivered = 0d; result.carbsDelivered = 0d; - result.comment = MainApp.instance().getString(R.string.danar_invalidinput); + result.comment = MainApp.gs(R.string.danar_invalidinput); log.error("deliverTreatment: Invalid input"); return result; } @@ -161,7 +186,7 @@ public class DanaRKoreanPlugin extends AbstractDanaRPlugin { // This is called from APS @Override - public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean enforceNew) { + public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, Profile profile, boolean enforceNew) { // Recheck pump status if older than 30 min //This should not be needed while using queue because connection should be done before calling this //if (pump.lastConnection.getTime() + 30 * 60 * 1000L < System.currentTimeMillis()) { @@ -170,23 +195,26 @@ public class DanaRKoreanPlugin extends AbstractDanaRPlugin { PumpEnactResult result = new PumpEnactResult(); - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - absoluteRate = configBuilderPlugin.applyBasalConstraints(absoluteRate); + absoluteRate = MainApp.getConstraintChecker().applyBasalConstraints(new Constraint<>(absoluteRate), profile).value(); final boolean doTempOff = getBaseBasalRate() - absoluteRate == 0d; final boolean doLowTemp = absoluteRate < getBaseBasalRate(); final boolean doHighTemp = absoluteRate > getBaseBasalRate() && !useExtendedBoluses; final boolean doExtendedTemp = absoluteRate > getBaseBasalRate() && useExtendedBoluses; + long now = System.currentTimeMillis(); + TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getRealTempBasalFromHistory(now); + ExtendedBolus activeExtended = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(now); + if (doTempOff) { // If extended in progress - if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress() && useExtendedBoluses) { + if (activeExtended != null && useExtendedBoluses) { if (Config.logPumpActions) log.debug("setTempBasalAbsolute: Stopping extended bolus (doTempOff)"); return cancelExtendedBolus(); } // If temp in progress - if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) { + if (activeTemp != null) { if (Config.logPumpActions) log.debug("setTempBasalAbsolute: Stopping temp basal (doTempOff)"); return cancelRealTempBasal(); @@ -212,7 +240,7 @@ public class DanaRKoreanPlugin extends AbstractDanaRPlugin { log.debug("setTempBasalAbsolute: Calculated percent rate: " + percentRate); // If extended in progress - if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress() && useExtendedBoluses) { + if (activeExtended != null && useExtendedBoluses) { if (Config.logPumpActions) log.debug("setTempBasalAbsolute: Stopping extended bolus (doLowTemp || doHighTemp)"); result = cancelExtendedBolus(); @@ -222,20 +250,18 @@ public class DanaRKoreanPlugin extends AbstractDanaRPlugin { } } // Check if some temp is already in progress - if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) { + if (activeTemp != null) { // Correct basal already set ? - TemporaryBasal running = MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()); if (Config.logPumpActions) - log.debug("setTempBasalAbsolute: currently running: " + running.toString()); - if (running.percentRate == percentRate) { + log.debug("setTempBasalAbsolute: currently running: " + activeTemp.toString()); + if (activeTemp.percentRate == percentRate) { if (enforceNew) { cancelTempBasal(true); } else { result.success = true; result.percent = percentRate; - result.absolute = MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory(); result.enacted = false; - result.duration = ((Double) MainApp.getConfigBuilder().getTempBasalRemainingMinutesFromHistory()).intValue(); + result.duration = activeTemp.getPlannedRemainingMinutes(); result.isPercent = true; result.isTempCancel = false; if (Config.logPumpActions) @@ -247,11 +273,11 @@ public class DanaRKoreanPlugin extends AbstractDanaRPlugin { // Convert duration from minutes to hours if (Config.logPumpActions) log.debug("setTempBasalAbsolute: Setting temp basal " + percentRate + "% for " + durationInMinutes + " mins (doLowTemp || doHighTemp)"); - return setTempBasalPercent(percentRate, durationInMinutes, false); + return setTempBasalPercent(percentRate, durationInMinutes, profile, false); } if (doExtendedTemp) { // Check if some temp is already in progress - if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) { + if (activeTemp != null) { if (Config.logPumpActions) log.debug("setTempBasalAbsolute: Stopping temp basal (doExtendedTemp)"); result = cancelRealTempBasal(); @@ -266,18 +292,18 @@ public class DanaRKoreanPlugin extends AbstractDanaRPlugin { Integer durationInHalfHours = Math.max(durationInMinutes / 30, 1); // We keep current basal running so need to sub current basal Double extendedRateToSet = absoluteRate - getBaseBasalRate(); - extendedRateToSet = configBuilderPlugin.applyBasalConstraints(extendedRateToSet); + extendedRateToSet = MainApp.getConstraintChecker().applyBasalConstraints(new Constraint<>(extendedRateToSet), profile).value(); // needs to be rounded to 0.1 extendedRateToSet = Round.roundTo(extendedRateToSet, pumpDescription.extendedBolusStep * 2); // *2 because of halfhours // What is current rate of extended bolusing in u/h? if (Config.logPumpActions) { - log.debug("setTempBasalAbsolute: Extended bolus in progress: " + MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress() + " rate: " + pump.extendedBolusAbsoluteRate + "U/h duration remaining: " + pump.extendedBolusRemainingMinutes + "min"); + log.debug("setTempBasalAbsolute: Extended bolus in progress: " + (activeExtended != null) + " rate: " + pump.extendedBolusAbsoluteRate + "U/h duration remaining: " + pump.extendedBolusRemainingMinutes + "min"); log.debug("setTempBasalAbsolute: Rate to set: " + extendedRateToSet + "U/h"); } // Compare with extended rate in progress - if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress() && Math.abs(pump.extendedBolusAbsoluteRate - extendedRateToSet) < getPumpDescription().extendedBolusStep) { + if (activeExtended != null && Math.abs(pump.extendedBolusAbsoluteRate - extendedRateToSet) < getPumpDescription().extendedBolusStep) { // correct extended already set result.success = true; result.absolute = pump.extendedBolusAbsoluteRate; @@ -313,23 +339,22 @@ public class DanaRKoreanPlugin extends AbstractDanaRPlugin { @Override public PumpEnactResult cancelTempBasal(boolean force) { - if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) + if (TreatmentsPlugin.getPlugin().isInHistoryRealTempBasalInProgress()) return cancelRealTempBasal(); - if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress() && useExtendedBoluses) { - PumpEnactResult cancelEx = cancelExtendedBolus(); - return cancelEx; + if (TreatmentsPlugin.getPlugin().isInHistoryExtendedBoluslInProgress() && useExtendedBoluses) { + return cancelExtendedBolus(); } PumpEnactResult result = new PumpEnactResult(); result.success = true; result.enacted = false; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.comment = MainApp.gs(R.string.virtualpump_resultok); result.isTempCancel = true; return result; } public PumpEnactResult cancelRealTempBasal() { PumpEnactResult result = new PumpEnactResult(); - TemporaryBasal runningTB = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); + TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); if (runningTB != null) { sExecutionService.tempBasalStop(); result.enacted = true; @@ -338,13 +363,13 @@ public class DanaRKoreanPlugin extends AbstractDanaRPlugin { if (!pump.isTempBasalInProgress) { result.success = true; result.isTempCancel = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.comment = MainApp.gs(R.string.virtualpump_resultok); if (Config.logPumpActions) log.debug("cancelRealTempBasal: OK"); return result; } else { result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_valuenotsetproperly); + result.comment = MainApp.gs(R.string.danar_valuenotsetproperly); result.isTempCancel = true; log.error("cancelRealTempBasal: Failed to cancel temp basal"); return result; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgInitConnStatusTime_k.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgInitConnStatusTime_k.java index 3cf47b656f..2028411791 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgInitConnStatusTime_k.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgInitConnStatusTime_k.java @@ -9,10 +9,10 @@ import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.events.EventRefreshGui; -import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPlugin; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MessageBase; @@ -29,23 +29,24 @@ public class MsgInitConnStatusTime_k extends MessageBase { public void handleMessage(byte[] bytes) { if (bytes.length - 10 < 10) { - Notification notification = new Notification(Notification.WRONG_DRIVER, MainApp.sResources.getString(R.string.pumpdrivercorrected), Notification.NORMAL); + Notification notification = new Notification(Notification.WRONG_DRIVER, MainApp.sResources.getString(R.string.pumpdrivercorrected), Notification.NORMAL); MainApp.bus().post(new EventNewNotification(notification)); DanaRKoreanPlugin.getPlugin().disconnect("Wrong Model"); log.debug("Wrong model selected. Switching to export DanaR"); - MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setFragmentEnabled(PluginBase.PUMP, false); - MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setFragmentVisible(PluginBase.PUMP, false); - MainApp.getSpecificPlugin(DanaRPlugin.class).setFragmentEnabled(PluginBase.PUMP, true); - MainApp.getSpecificPlugin(DanaRPlugin.class).setFragmentVisible(PluginBase.PUMP, true); - DanaRPump.getInstance().lastConnection = 0; // mark not initialized + MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setPluginEnabled(PluginType.PUMP, false); + MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setFragmentVisible(PluginType.PUMP, false); + MainApp.getSpecificPlugin(DanaRPlugin.class).setPluginEnabled(PluginType.PUMP, true); + MainApp.getSpecificPlugin(DanaRPlugin.class).setFragmentVisible(PluginType.PUMP, true); + + DanaRPump.reset(); // mark not initialized //If profile coming from pump, switch it as well - if (MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).isEnabled(PluginBase.PROFILE)) { - (MainApp.getSpecificPlugin(DanaRKoreanPlugin.class)).setFragmentEnabled(PluginBase.PROFILE, false); - (MainApp.getSpecificPlugin(DanaRPlugin.class)).setFragmentEnabled(PluginBase.PROFILE, true); + if (MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).isEnabled(PluginType.PROFILE)) { + (MainApp.getSpecificPlugin(DanaRKoreanPlugin.class)).setPluginEnabled(PluginType.PROFILE, false); + (MainApp.getSpecificPlugin(DanaRPlugin.class)).setPluginEnabled(PluginType.PROFILE, true); } - MainApp.getConfigBuilder().storeSettings(); + MainApp.getConfigBuilder().storeSettings("ChangingKoreanDanaDriver"); MainApp.bus().post(new EventRefreshGui()); ConfigBuilderPlugin.getCommandQueue().readStatus("PumpDriverChange", null); // force new connection return; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/services/DanaRKoreanExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/services/DanaRKoreanExecutionService.java index 6d1ccc132d..106074ca3c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/services/DanaRKoreanExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/services/DanaRKoreanExecutionService.java @@ -18,7 +18,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventInitializationChanged; import info.nightscout.androidaps.events.EventPreferenceChange; @@ -264,29 +264,31 @@ public class DanaRKoreanExecutionService extends AbstractDanaRExecutionService { mSerialIOThread.sendMessage(new MsgSetCarbsEntry(carbtime, carbs)); } - MsgBolusProgress progress = new MsgBolusProgress(amount, t); // initialize static variables - long bolusStart = System.currentTimeMillis(); + if (amount > 0) { + MsgBolusProgress progress = new MsgBolusProgress(amount, t); // initialize static variables + long bolusStart = System.currentTimeMillis(); - if (!stop.stopped) { - mSerialIOThread.sendMessage(start); - } else { - t.insulin = 0d; - return false; - } - while (!stop.stopped && !start.failed) { - SystemClock.sleep(100); - if ((System.currentTimeMillis() - progress.lastReceive) > 15 * 1000L) { // if i didn't receive status for more than 15 sec expecting broken comm - stop.stopped = true; - stop.forced = true; - log.debug("Communication stopped"); + if (!stop.stopped) { + mSerialIOThread.sendMessage(start); + } else { + t.insulin = 0d; + return false; } + while (!stop.stopped && !start.failed) { + SystemClock.sleep(100); + if ((System.currentTimeMillis() - progress.lastReceive) > 15 * 1000L) { // if i didn't receive status for more than 15 sec expecting broken comm + stop.stopped = true; + stop.forced = true; + log.debug("Communication stopped"); + } + } + SystemClock.sleep(300); + + mBolusingTreatment = null; + ConfigBuilderPlugin.getCommandQueue().readStatus("bolusOK", null); } - SystemClock.sleep(300); - mBolusingTreatment = null; - ConfigBuilderPlugin.getCommandQueue().readStatus("bolusOK", null); - - return true; + return !start.failed; } public boolean carbsEntry(int amount) { @@ -301,6 +303,11 @@ public class DanaRKoreanExecutionService extends AbstractDanaRExecutionService { return false; } + @Override + public boolean tempBasalShortDuration(int percent, int durationInMinutes) { + return false; + } + public boolean updateBasalsInPump(final Profile profile) { if (!isConnected()) return false; MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.updatingbasalrates))); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/DanaRSPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/DanaRSPlugin.java index 9978880e08..a2ec13d184 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/DanaRSPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/DanaRSPlugin.java @@ -15,11 +15,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; -import java.util.Objects; import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.Config; -import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; @@ -28,24 +26,29 @@ import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.TemporaryBasal; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.PumpDanaRS.comm.DanaRS_Packet_Bolus_Set_Step_Bolus_Start; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.androidaps.events.EventAppExit; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.ConstraintsInterface; import info.nightscout.androidaps.interfaces.DanaRInterface; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.ProfileInterface; import info.nightscout.androidaps.interfaces.PumpDescription; import info.nightscout.androidaps.interfaces.PumpInterface; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.ConfigBuilder.DetailedBolusInfoStorage; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.ProfileNS.NSProfilePlugin; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRFragment; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.RecordTypes; import info.nightscout.androidaps.plugins.PumpDanaRS.events.EventDanaRSDeviceChange; import info.nightscout.androidaps.plugins.PumpDanaRS.services.DanaRSService; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.Round; @@ -55,103 +58,9 @@ import info.nightscout.utils.SP; * Created by mike on 03.09.2017. */ -public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, ConstraintsInterface, ProfileInterface { +public class DanaRSPlugin extends PluginBase implements PumpInterface, DanaRInterface, ConstraintsInterface, ProfileInterface { private static Logger log = LoggerFactory.getLogger(DanaRSPlugin.class); - - @Override - public int getType() { - return PluginBase.PUMP; - } - - @Override - public String getFragmentClass() { - return DanaRFragment.class.getName(); - } - - @Override - public String getName() { - return MainApp.instance().getString(R.string.danarspump); - } - - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.danarspump_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - if (type == PluginBase.PROFILE) return fragmentProfileEnabled && fragmentPumpEnabled; - else if (type == PluginBase.PUMP) return fragmentPumpEnabled; - else if (type == PluginBase.CONSTRAINTS) return fragmentPumpEnabled; - return false; - } - - @Override - public boolean isVisibleInTabs(int type) { - if (type == PluginBase.PROFILE || type == PluginBase.CONSTRAINTS) return false; - else if (type == PluginBase.PUMP) return fragmentPumpVisible; - return false; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return type == PUMP; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == PluginBase.PROFILE) - this.fragmentProfileEnabled = fragmentEnabled; - else if (type == PluginBase.PUMP) - this.fragmentPumpEnabled = fragmentEnabled; - // if pump profile was enabled need to switch to another too - if (type == PluginBase.PUMP && !fragmentEnabled && this.fragmentProfileEnabled) { - setFragmentEnabled(PluginBase.PROFILE, false); - setFragmentVisible(PluginBase.PROFILE, false); - MainApp.getSpecificPlugin(NSProfilePlugin.class).setFragmentEnabled(PluginBase.PROFILE, true); - MainApp.getSpecificPlugin(NSProfilePlugin.class).setFragmentVisible(PluginBase.PROFILE, true); - } - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == PluginBase.PUMP) - this.fragmentPumpVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return R.xml.pref_danars; - } - - static boolean fragmentPumpEnabled = false; - static boolean fragmentProfileEnabled = false; - static boolean fragmentPumpVisible = false; - - public static DanaRSService danaRSService; - - public static String mDeviceAddress = ""; - public static String mDeviceName = ""; - private static DanaRSPlugin plugin = null; - private static DanaRPump pump = DanaRPump.getInstance(); - public static PumpDescription pumpDescription = new PumpDescription(); public static DanaRSPlugin getPlugin() { if (plugin == null) @@ -159,12 +68,22 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, return plugin; } + public static DanaRSService danaRSService; + + public static String mDeviceAddress = ""; + public static String mDeviceName = ""; + + private static DanaRPump pump = DanaRPump.getInstance(); + public static PumpDescription pumpDescription = new PumpDescription(); + DanaRSPlugin() { - Context context = MainApp.instance().getApplicationContext(); - Intent intent = new Intent(context, DanaRSService.class); - context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); - MainApp.bus().register(this); - onStatusEvent(new EventDanaRSDeviceChange()); // load device name + super(new PluginDescription() + .mainType(PluginType.PUMP) + .fragmentClass(DanaRFragment.class.getName()) + .pluginName(R.string.danarspump) + .shortName(R.string.danarspump_shortname) + .preferencesId(R.xml.pref_danars) + ); pumpDescription.isBolusCapable = true; pumpDescription.bolusStep = 0.05d; @@ -181,6 +100,8 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, pumpDescription.tempPercentStep = 10; pumpDescription.tempDurationStep = 60; + pumpDescription.tempDurationStep15mAllowed = true; + pumpDescription.tempDurationStep30mAllowed = true; pumpDescription.tempMaxDuration = 24 * 60; @@ -191,6 +112,38 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, pumpDescription.isRefillingCapable = true; pumpDescription.storesCarbInfo = true; + + pumpDescription.supportsTDDs = true; + pumpDescription.needsManualTDDLoad = true; + } + + @Override + public void onStateChange(PluginType type, State oldState, State newState) { + // if pump profile was enabled need to switch to another too + if (type == PluginType.PUMP && newState == State.DISABLED && isProfileInterfaceEnabled) { + setPluginEnabled(PluginType.PROFILE, false); + NSProfilePlugin.getPlugin().setPluginEnabled(PluginType.PROFILE, true); + NSProfilePlugin.getPlugin().setFragmentVisible(PluginType.PROFILE, true); + } + } + + @Override + protected void onStart() { + Context context = MainApp.instance().getApplicationContext(); + Intent intent = new Intent(context, DanaRSService.class); + context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + + MainApp.bus().register(this); + onStatusEvent(new EventDanaRSDeviceChange()); // load device name + super.onStart(); + } + + @Override + protected void onStop() { + Context context = MainApp.instance().getApplicationContext(); + context.unbindService(mConnection); + + MainApp.bus().unregister(this); } private ServiceConnection mConnection = new ServiceConnection() { @@ -226,8 +179,6 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, final Object o = new Object(); danaRSService.connect(from, mDeviceAddress, o); - pumpDescription.basalStep = pump.basalStep; - pumpDescription.bolusStep = pump.bolusStep; } } @@ -253,8 +204,11 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, @Override public void getPumpStatus() { - if (danaRSService != null) + if (danaRSService != null) { danaRSService.getPumpStatus(); + pumpDescription.basalStep = pump.basalStep; + pumpDescription.bolusStep = pump.bolusStep; + } } // DanaR interface @@ -272,77 +226,28 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, // Constraints interface @Override - public boolean isLoopEnabled() { - return true; - } - - @Override - public boolean isClosedModeEnabled() { - return true; - } - - @Override - public boolean isAutosensModeEnabled() { - return true; - } - - @Override - public boolean isAMAModeEnabled() { - return true; - } - - @Override - public boolean isSMBModeEnabled() { - return true; - } - - @Override - public Double applyBasalConstraints(Double absoluteRate) { - double origAbsoluteRate = absoluteRate; - if (pump != null) { - if (absoluteRate > pump.maxBasal) { - absoluteRate = pump.maxBasal; - if (Config.logConstraintsChanges && origAbsoluteRate != Constants.basalAbsoluteOnlyForCheckLimit) - log.debug("Limiting rate " + origAbsoluteRate + "U/h by pump constraint to " + absoluteRate + "U/h"); - } - } + public Constraint applyBasalConstraints(Constraint absoluteRate, Profile profile) { + if (pump != null) + absoluteRate.setIfSmaller(pump.maxBasal, String.format(MainApp.gs(R.string.limitingbasalratio), pump.maxBasal, MainApp.gs(R.string.pumplimit)), this); return absoluteRate; } @Override - public Integer applyBasalConstraints(Integer percentRate) { - Integer origPercentRate = percentRate; - if (percentRate < 0) percentRate = 0; - if (percentRate > getPumpDescription().maxTempPercent) - percentRate = getPumpDescription().maxTempPercent; - if (!Objects.equals(percentRate, origPercentRate) && Config.logConstraintsChanges && !Objects.equals(origPercentRate, Constants.basalPercentOnlyForCheckLimit)) - log.debug("Limiting percent rate " + origPercentRate + "% to " + percentRate + "%"); + public Constraint applyBasalPercentConstraints(Constraint percentRate, Profile profile) { + percentRate.setIfGreater(0, String.format(MainApp.gs(R.string.limitingpercentrate), 0, MainApp.gs(R.string.itmustbepositivevalue)), this); + percentRate.setIfSmaller(getPumpDescription().maxTempPercent, String.format(MainApp.gs(R.string.limitingpercentrate), getPumpDescription().maxTempPercent, MainApp.gs(R.string.pumplimit)), this); + return percentRate; } + @Override - public Double applyBolusConstraints(Double insulin) { - double origInsulin = insulin; - if (pump != null) { - if (insulin > pump.maxBolus) { - insulin = pump.maxBolus; - if (Config.logConstraintsChanges && origInsulin != Constants.bolusOnlyForCheckLimit) - log.debug("Limiting bolus " + origInsulin + "U by pump constraint to " + insulin + "U"); - } - } + public Constraint applyBolusConstraints(Constraint insulin) { + if (pump != null) + insulin.setIfSmaller(pump.maxBolus, String.format(MainApp.gs(R.string.limitingbolus), pump.maxBolus, MainApp.gs(R.string.pumplimit)), this); return insulin; } - @Override - public Integer applyCarbsConstraints(Integer carbs) { - return carbs; - } - - @Override - public Double applyMaxIOBConstraints(Double maxIob) { - return maxIob; - } - // Profile interface @Nullable @@ -367,7 +272,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, @Override public boolean isInitialized() { - return pump.lastConnection > 0 && pump.maxBasal > 0; + return pump.lastConnection > 0 && pump.maxBasal > 0; } @Override @@ -426,7 +331,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, int basalIncrement = pump.basal48Enable ? 30 * 60 : 60 * 60; for (int h = 0; h < basalValues; h++) { Double pumpValue = pump.pumpProfiles[pump.activeProfile][h]; - Double profileValue = profile.getBasal((Integer) (h * basalIncrement)); + Double profileValue = profile.getBasalTimeFromMidnight((Integer) (h * basalIncrement)); if (profileValue == null) return true; if (Math.abs(pumpValue - profileValue) > getPumpDescription().basalStep) { log.debug("Diff found. Hour: " + h + " Pump: " + pumpValue + " Profile: " + profileValue); @@ -448,8 +353,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, @Override public synchronized PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - detailedBolusInfo.insulin = configBuilderPlugin.applyBolusConstraints(detailedBolusInfo.insulin); + detailedBolusInfo.insulin = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(detailedBolusInfo.insulin)).value(); if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) { int preferencesSpeed = SP.getInt(R.string.key_danars_bolusspeed, 0); int speed = 12; @@ -464,9 +368,9 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, speed = 60; break; } - // v2 stores end time for bolus, we need to adjust time + // RS stores end time for bolus, we need to adjust time // default delivery speed is 12 sec/U - detailedBolusInfo.date += detailedBolusInfo.insulin * speed * 1000; + detailedBolusInfo.date = DateUtil.now() + (long) (speed * detailedBolusInfo.insulin * 1000); // clean carbs to prevent counting them as twice because they will picked up as another record // I don't think it's necessary to copy DetailedBolusInfo right now for carbs records double carbs = detailedBolusInfo.carbs; @@ -477,14 +381,18 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, DetailedBolusInfoStorage.add(detailedBolusInfo); // will be picked up on reading history Treatment t = new Treatment(); + t.isSMB = detailedBolusInfo.isSMB; boolean connectionOK = false; if (detailedBolusInfo.insulin > 0 || carbs > 0) - connectionOK = danaRSService.bolus(detailedBolusInfo.insulin, (int) carbs, System.currentTimeMillis() + carbTime * 60 * 1000 + 30000, t); // +30s to make the record different + connectionOK = danaRSService.bolus(detailedBolusInfo.insulin, (int) carbs, DateUtil.now() + carbTime * 60 * 1000, t); PumpEnactResult result = new PumpEnactResult(); - result.success = connectionOK; + result.success = connectionOK && detailedBolusInfo.insulin == t.insulin; result.bolusDelivered = t.insulin; result.carbsDelivered = detailedBolusInfo.carbs; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + if (!result.success) + result.comment = String.format(MainApp.gs(R.string.boluserrorcode), detailedBolusInfo.insulin, t.insulin, DanaRS_Packet_Bolus_Set_Step_Bolus_Start.errorCode); + else + result.comment = MainApp.gs(R.string.virtualpump_resultok); if (Config.logPumpActions) log.debug("deliverTreatment: OK. Asked: " + detailedBolusInfo.insulin + " Delivered: " + result.bolusDelivered); return result; @@ -493,7 +401,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, result.success = false; result.bolusDelivered = 0d; result.carbsDelivered = 0d; - result.comment = MainApp.instance().getString(R.string.danar_invalidinput); + result.comment = MainApp.gs(R.string.danar_invalidinput); log.error("deliverTreatment: Invalid input"); return result; } @@ -510,7 +418,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, // This is called from APS @Override - public synchronized PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean enforceNew) { + public synchronized PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, Profile profile, boolean enforceNew) { // Recheck pump status if older than 30 min //This should not be needed while using queue because connection should be done before calling this @@ -520,8 +428,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, PumpEnactResult result = new PumpEnactResult(); - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - absoluteRate = configBuilderPlugin.applyBasalConstraints(absoluteRate); + absoluteRate = MainApp.getConstraintChecker().applyBasalConstraints(new Constraint<>(absoluteRate), profile).value(); final boolean doTempOff = getBaseBasalRate() - absoluteRate == 0d; final boolean doLowTemp = absoluteRate < getBaseBasalRate(); @@ -529,7 +436,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, if (doTempOff) { // If temp in progress - if (MainApp.getConfigBuilder().isTempBasalInProgress()) { + if (TreatmentsPlugin.getPlugin().isTempBasalInProgress()) { if (Config.logPumpActions) log.debug("setTempBasalAbsolute: Stopping temp basal (doTempOff)"); return cancelTempBasal(false); @@ -551,15 +458,17 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, if (percentRate > 500) // Special high temp 500/15min percentRate = 500; // Check if some temp is already in progress - if (MainApp.getConfigBuilder().isTempBasalInProgress()) { + TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); + if (activeTemp != null) { + if (Config.logPumpActions) + log.debug("setTempBasalAbsolute: currently running: " + activeTemp.toString()); // Correct basal already set ? - if (MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()).percentRate == percentRate) { + if (activeTemp.percentRate == percentRate) { if (!enforceNew) { result.success = true; result.percent = percentRate; - result.absolute = MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory(); result.enacted = false; - result.duration = ((Double) MainApp.getConfigBuilder().getTempBasalRemainingMinutesFromHistory()).intValue(); + result.duration = activeTemp.getPlannedRemainingMinutes(); result.isPercent = true; result.isTempCancel = false; if (Config.logPumpActions) @@ -589,44 +498,47 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, } @Override - public synchronized PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, boolean enforceNew) { + public synchronized PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, Profile profile, boolean enforceNew) { PumpEnactResult result = new PumpEnactResult(); - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - percent = configBuilderPlugin.applyBasalConstraints(percent); + percent = MainApp.getConstraintChecker().applyBasalPercentConstraints(new Constraint<>(percent), profile).value(); if (percent < 0) { result.isTempCancel = false; result.enacted = false; result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_invalidinput); + result.comment = MainApp.gs(R.string.danar_invalidinput); log.error("setTempBasalPercent: Invalid input"); return result; } if (percent > getPumpDescription().maxTempPercent) percent = getPumpDescription().maxTempPercent; - TemporaryBasal runningTB = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); + long now = System.currentTimeMillis(); + TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(now); if (runningTB != null && runningTB.percentRate == percent && !enforceNew) { result.enacted = false; result.success = true; result.isTempCancel = false; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.comment = MainApp.gs(R.string.virtualpump_resultok); result.duration = pump.tempBasalRemainingMin; result.percent = pump.tempBasalPercent; - result.absolute = MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory(); result.isPercent = true; if (Config.logPumpActions) log.debug("setTempBasalPercent: Correct value already set"); return result; } - int durationInHours = Math.max(durationInMinutes / 60, 1); - boolean connectionOK = danaRSService.tempBasal(percent, durationInHours); + boolean connectionOK; + if (durationInMinutes == 15 || durationInMinutes == 30) { + connectionOK = danaRSService.tempBasalShortDuration(percent, durationInMinutes); + } else { + int durationInHours = Math.max(durationInMinutes / 60, 1); + connectionOK = danaRSService.tempBasal(percent, durationInHours); + } if (connectionOK && pump.isTempBasalInProgress && pump.tempBasalPercent == percent) { result.enacted = true; result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.comment = MainApp.gs(R.string.virtualpump_resultok); result.isTempCancel = false; result.duration = pump.tempBasalRemainingMin; result.percent = pump.tempBasalPercent; - result.absolute = MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory(); result.isPercent = true; if (Config.logPumpActions) log.debug("setTempBasalPercent: OK"); @@ -634,7 +546,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, } result.enacted = false; result.success = false; - result.comment = MainApp.instance().getString(R.string.tempbasaldeliveryerror); + result.comment = MainApp.gs(R.string.tempbasaldeliveryerror); log.error("setTempBasalPercent: Failed to set temp basal"); return result; } @@ -645,7 +557,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, if (connectionOK && pump.isTempBasalInProgress && pump.tempBasalPercent == percent) { result.enacted = true; result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.comment = MainApp.gs(R.string.virtualpump_resultok); result.isTempCancel = false; result.duration = pump.tempBasalRemainingMin; result.percent = pump.tempBasalPercent; @@ -656,24 +568,23 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, } result.enacted = false; result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_valuenotsetproperly); + result.comment = MainApp.gs(R.string.danar_valuenotsetproperly); log.error("setHighTempBasalPercent: Failed to set temp basal"); return result; } @Override public synchronized PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes) { - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - insulin = configBuilderPlugin.applyBolusConstraints(insulin); + insulin = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(insulin)).value(); // needs to be rounded int durationInHalfHours = Math.max(durationInMinutes / 30, 1); insulin = Round.roundTo(insulin, getPumpDescription().extendedBolusStep); PumpEnactResult result = new PumpEnactResult(); - ExtendedBolus runningEB = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); + ExtendedBolus runningEB = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(System.currentTimeMillis()); if (runningEB != null && Math.abs(runningEB.insulin - insulin) < getPumpDescription().extendedBolusStep) { result.enacted = false; result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.comment = MainApp.gs(R.string.virtualpump_resultok); result.duration = pump.extendedBolusRemainingMinutes; result.absolute = pump.extendedBolusAbsoluteRate; result.isPercent = false; @@ -686,7 +597,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, if (connectionOK && pump.isExtendedInProgress && Math.abs(pump.extendedBolusAbsoluteRate - insulin) < getPumpDescription().extendedBolusStep) { result.enacted = true; result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.comment = MainApp.gs(R.string.virtualpump_resultok); result.isTempCancel = false; result.duration = pump.extendedBolusRemainingMinutes; result.absolute = pump.extendedBolusAbsoluteRate; @@ -698,7 +609,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, } result.enacted = false; result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_valuenotsetproperly); + result.comment = MainApp.gs(R.string.danar_valuenotsetproperly); log.error("setExtendedBolus: Failed to extended bolus"); return result; } @@ -706,7 +617,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, @Override public synchronized PumpEnactResult cancelTempBasal(boolean force) { PumpEnactResult result = new PumpEnactResult(); - TemporaryBasal runningTB = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); + TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); if (runningTB != null) { danaRSService.tempBasalStop(); result.enacted = true; @@ -715,13 +626,13 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, if (!pump.isTempBasalInProgress) { result.success = true; result.isTempCancel = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.comment = MainApp.gs(R.string.virtualpump_resultok); if (Config.logPumpActions) log.debug("cancelRealTempBasal: OK"); return result; } else { result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_valuenotsetproperly); + result.comment = MainApp.gs(R.string.danar_valuenotsetproperly); result.isTempCancel = true; log.error("cancelRealTempBasal: Failed to cancel temp basal"); return result; @@ -731,7 +642,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, @Override public synchronized PumpEnactResult cancelExtendedBolus() { PumpEnactResult result = new PumpEnactResult(); - ExtendedBolus runningEB = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); + ExtendedBolus runningEB = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(System.currentTimeMillis()); if (runningEB != null) { danaRSService.extendedBolusStop(); result.enacted = true; @@ -739,20 +650,21 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, } if (!pump.isExtendedInProgress) { result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.comment = MainApp.gs(R.string.virtualpump_resultok); if (Config.logPumpActions) log.debug("cancelExtendedBolus: OK"); return result; } else { result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_valuenotsetproperly); + result.comment = MainApp.gs(R.string.danar_valuenotsetproperly); log.error("cancelExtendedBolus: Failed to cancel extended bolus"); return result; } } @Override - public JSONObject getJSONStatus() { + public JSONObject getJSONStatus(Profile profile, String profileName) { + long now = System.currentTimeMillis(); if (pump.lastConnection + 5 * 60 * 1000L < System.currentTimeMillis()) { return null; } @@ -770,13 +682,13 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, extended.put("LastBolus", pump.lastBolusTime.toLocaleString()); extended.put("LastBolusAmount", pump.lastBolusAmount); } - TemporaryBasal tb = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); + TemporaryBasal tb = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(now); if (tb != null) { - extended.put("TempBasalAbsoluteRate", tb.tempBasalConvertedToAbsolute(System.currentTimeMillis())); + extended.put("TempBasalAbsoluteRate", tb.tempBasalConvertedToAbsolute(now, profile)); extended.put("TempBasalStart", DateUtil.dateAndTimeString(tb.date)); extended.put("TempBasalRemaining", tb.getPlannedRemainingMinutes()); } - ExtendedBolus eb = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); + ExtendedBolus eb = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(now); if (eb != null) { extended.put("ExtendedBolusAbsoluteRate", eb.absoluteRate()); extended.put("ExtendedBolusStart", DateUtil.dateAndTimeString(eb.date)); @@ -792,7 +704,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, pumpjson.put("status", status); pumpjson.put("extended", extended); pumpjson.put("reservoir", (int) pump.reservoirRemainingUnits); - pumpjson.put("clock", DateUtil.toISOString(new Date())); + pumpjson.put("clock", DateUtil.toISOString(now)); } catch (JSONException e) { log.error("Unhandled exception", e); } @@ -820,11 +732,13 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, if (pump.lastBolusTime.getTime() != 0) { ret += "LastBolus: " + DecimalFormatter.to2Decimal(pump.lastBolusAmount) + "U @" + android.text.format.DateFormat.format("HH:mm", pump.lastBolusTime) + "\n"; } - if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) { - ret += "Temp: " + MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()).toStringFull() + "\n"; + TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getRealTempBasalFromHistory(System.currentTimeMillis()); + if (activeTemp != null) { + ret += "Temp: " + activeTemp.toStringFull() + "\n"; } - if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) { - ret += "Extended: " + MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()).toString() + "\n"; + ExtendedBolus activeExtendedBolus = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(System.currentTimeMillis()); + if (activeExtendedBolus != null) { + ret += "Extended: " + activeExtendedBolus.toString() + "\n"; } if (!veryShort) { ret += "TDD: " + DecimalFormatter.to0Decimal(pump.dailyTotalUnits) + " / " + pump.maxDailyTotalUnits + " U\n"; @@ -839,4 +753,9 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, public boolean isFakingTempsByExtendedBoluses() { return false; } + + @Override + public PumpEnactResult loadTDDs() { + return loadHistory(RecordTypes.RECORD_TYPE_DAILY); + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_APS_Basal_Set_Temporary_Basal.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_APS_Basal_Set_Temporary_Basal.java index 06608b5a74..5da3655ceb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_APS_Basal_Set_Temporary_Basal.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_APS_Basal_Set_Temporary_Basal.java @@ -10,33 +10,60 @@ import info.nightscout.androidaps.Config; public class DanaRS_Packet_APS_Basal_Set_Temporary_Basal extends DanaRS_Packet { private static Logger log = LoggerFactory.getLogger(DanaRS_Packet_APS_Basal_Set_Temporary_Basal.class); - private int temporaryBasalRatio; - private int temporaryBasalDuration; + int temporaryBasalRatio; + int temporaryBasalDuration; public int error; - public DanaRS_Packet_APS_Basal_Set_Temporary_Basal() { + final int PARAM30MIN = 160; + final int PARAM15MIN = 150; + + DanaRS_Packet_APS_Basal_Set_Temporary_Basal() { super(); opCode = BleCommandUtil.DANAR_PACKET__OPCODE_BASAL__APS_SET_TEMPORARY_BASAL; } public DanaRS_Packet_APS_Basal_Set_Temporary_Basal(int percent) { this(); + setParams(percent); + } + protected void setParams(int percent) { //HARDCODED LIMITS if (percent < 0) percent = 0; if (percent > 500) percent = 500; temporaryBasalRatio = percent; if (percent < 100) { - temporaryBasalDuration = 160; + temporaryBasalDuration = PARAM30MIN; if (Config.logDanaMessageDetail) log.debug("APS Temp basal start percent: " + percent + " duration 30 min"); } else { - temporaryBasalDuration = 150; + temporaryBasalDuration = PARAM15MIN; if (Config.logDanaMessageDetail) log.debug("APS Temp basal start percent: " + percent + " duration 15 min"); } + } + public DanaRS_Packet_APS_Basal_Set_Temporary_Basal(int percent, boolean fifteenMinutes, boolean thirtyMinutes ) { + this(); + setParams(percent, fifteenMinutes, thirtyMinutes); + } + + protected void setParams(int percent, boolean fifteenMinutes, boolean thirtyMinutes) { + //HARDCODED LIMITS + if (percent < 0) percent = 0; + if (percent > 500) percent = 500; + + temporaryBasalRatio = percent; + if (thirtyMinutes && percent <= 200) { // 30 min is allowed up to 200% + temporaryBasalDuration = PARAM30MIN; + if (Config.logDanaMessageDetail) + log.debug("APS Temp basal start percent: " + percent + " duration 30 min"); + } else { + temporaryBasalDuration = PARAM15MIN; + if (Config.logDanaMessageDetail) + log.debug("APS Temp basal start percent: " + percent + " duration 15 min"); + } } @Override @@ -55,6 +82,7 @@ public class DanaRS_Packet_APS_Basal_Set_Temporary_Basal extends DanaRS_Packet { failed = true; log.error("Set APS temp basal start result: " + result + " FAILED!!!"); } else { + failed = false; if (Config.logDanaMessageDetail) log.debug("Set APS temp basal start result: " + result); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_APS_History_Events.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_APS_History_Events.java index c11b75764c..a707c5e0cc 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_APS_History_Events.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_APS_History_Events.java @@ -18,6 +18,7 @@ import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.plugins.ConfigBuilder.DetailedBolusInfoStorage; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.DateUtil; public class DanaRS_Packet_APS_History_Events extends DanaRS_Packet { @@ -85,10 +86,7 @@ public class DanaRS_Packet_APS_History_Events extends DanaRS_Packet { int param1 = ((intFromBuff(data, 7, 1) << 8) & 0xFF00) + (intFromBuff(data, 8, 1) & 0xFF); int param2 = ((intFromBuff(data, 9, 1) << 8) & 0xFF00) + (intFromBuff(data, 10, 1) & 0xFF); - TemporaryBasal temporaryBasal = new TemporaryBasal(); - temporaryBasal.date = datetime.getTime(); - temporaryBasal.source = Source.PUMP; - temporaryBasal.pumpId = datetime.getTime(); + TemporaryBasal temporaryBasal = new TemporaryBasal().date(datetime.getTime()).source(Source.PUMP).pumpId(datetime.getTime()); ExtendedBolus extendedBolus = new ExtendedBolus(); extendedBolus.date = datetime.getTime(); @@ -113,36 +111,36 @@ public class DanaRS_Packet_APS_History_Events extends DanaRS_Packet { log.debug("EVENT TEMPSTART (" + recordCode + ") " + datetime.toLocaleString() + " Ratio: " + param1 + "% Duration: " + param2 + "min"); temporaryBasal.percentRate = param1; temporaryBasal.durationInMinutes = param2; - MainApp.getConfigBuilder().addToHistoryTempBasal(temporaryBasal); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(temporaryBasal); status = "TEMPSTART " + DateUtil.timeString(datetime); break; case DanaRPump.TEMPSTOP: log.debug("EVENT TEMPSTOP (" + recordCode + ") " + datetime.toLocaleString()); - MainApp.getConfigBuilder().addToHistoryTempBasal(temporaryBasal); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(temporaryBasal); status = "TEMPSTOP " + DateUtil.timeString(datetime); break; case DanaRPump.EXTENDEDSTART: log.debug("EVENT EXTENDEDSTART (" + recordCode + ") " + datetime.toLocaleString() + " Amount: " + (param1 / 100d) + "U Duration: " + param2 + "min"); extendedBolus.insulin = param1 / 100d; extendedBolus.durationInMinutes = param2; - MainApp.getConfigBuilder().addToHistoryExtendedBolus(extendedBolus); + TreatmentsPlugin.getPlugin().addToHistoryExtendedBolus(extendedBolus); status = "EXTENDEDSTART " + DateUtil.timeString(datetime); break; case DanaRPump.EXTENDEDSTOP: log.debug("EVENT EXTENDEDSTOP (" + recordCode + ") " + datetime.toLocaleString() + " Delivered: " + (param1 / 100d) + "U RealDuration: " + param2 + "min"); - MainApp.getConfigBuilder().addToHistoryExtendedBolus(extendedBolus); + TreatmentsPlugin.getPlugin().addToHistoryExtendedBolus(extendedBolus); status = "EXTENDEDSTOP " + DateUtil.timeString(datetime); break; case DanaRPump.BOLUS: detailedBolusInfo.insulin = param1 / 100d; - boolean newRecord = MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); + boolean newRecord = TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo); log.debug((newRecord ? "**NEW** " : "") + "EVENT BOLUS (" + recordCode + ") " + datetime.toLocaleString() + " Bolus: " + (param1 / 100d) + "U Duration: " + param2 + "min"); DetailedBolusInfoStorage.remove(detailedBolusInfo.date); status = "BOLUS " + DateUtil.timeString(datetime); break; case DanaRPump.DUALBOLUS: detailedBolusInfo.insulin = param1 / 100d; - newRecord = MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); + newRecord = TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo); log.debug((newRecord ? "**NEW** " : "") + "EVENT DUALBOLUS (" + recordCode + ") " + datetime.toLocaleString() + " Bolus: " + (param1 / 100d) + "U Duration: " + param2 + "min"); DetailedBolusInfoStorage.remove(detailedBolusInfo.date); status = "DUALBOLUS " + DateUtil.timeString(datetime); @@ -151,12 +149,12 @@ public class DanaRS_Packet_APS_History_Events extends DanaRS_Packet { log.debug("EVENT DUALEXTENDEDSTART (" + recordCode + ") " + datetime.toLocaleString() + " Amount: " + (param1 / 100d) + "U Duration: " + param2 + "min"); extendedBolus.insulin = param1 / 100d; extendedBolus.durationInMinutes = param2; - MainApp.getConfigBuilder().addToHistoryExtendedBolus(extendedBolus); + TreatmentsPlugin.getPlugin().addToHistoryExtendedBolus(extendedBolus); status = "DUALEXTENDEDSTART " + DateUtil.timeString(datetime); break; case DanaRPump.DUALEXTENDEDSTOP: log.debug("EVENT DUALEXTENDEDSTOP (" + recordCode + ") " + datetime.toLocaleString() + " Delivered: " + (param1 / 100d) + "U RealDuration: " + param2 + "min"); - MainApp.getConfigBuilder().addToHistoryExtendedBolus(extendedBolus); + TreatmentsPlugin.getPlugin().addToHistoryExtendedBolus(extendedBolus); status = "DUALEXTENDEDSTOP " + DateUtil.timeString(datetime); break; case DanaRPump.SUSPENDON: @@ -185,7 +183,7 @@ public class DanaRS_Packet_APS_History_Events extends DanaRS_Packet { emptyCarbsInfo.date = datetime.getTime(); emptyCarbsInfo.source = Source.PUMP; emptyCarbsInfo.pumpId = datetime.getTime(); - newRecord = MainApp.getConfigBuilder().addToHistoryTreatment(emptyCarbsInfo); + newRecord = TreatmentsPlugin.getPlugin().addToHistoryTreatment(emptyCarbsInfo); log.debug((newRecord ? "**NEW** " : "") + "EVENT CARBS (" + recordCode + ") " + datetime.toLocaleString() + " Carbs: " + param1 + "g"); status = "CARBS " + DateUtil.timeString(datetime); break; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_Bolus_Set_Step_Bolus_Start.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_Bolus_Set_Step_Bolus_Start.java index 4c5cc23ef3..f90dd77b74 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_Bolus_Set_Step_Bolus_Start.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_Bolus_Set_Step_Bolus_Start.java @@ -1,64 +1,69 @@ package info.nightscout.androidaps.plugins.PumpDanaRS.comm; +import com.cozmo.danar.util.BleCommandUtil; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; -import com.cozmo.danar.util.BleCommandUtil; -import info.nightscout.utils.HardLimits; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.interfaces.Constraint; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; public class DanaRS_Packet_Bolus_Set_Step_Bolus_Start extends DanaRS_Packet { - private static Logger log = LoggerFactory.getLogger(DanaRS_Packet_Bolus_Set_Step_Bolus_Start.class); + private static Logger log = LoggerFactory.getLogger(DanaRS_Packet_Bolus_Set_Step_Bolus_Start.class); - private double amount; - private int speed; + private double amount; + private int speed; - public boolean failed; + public boolean failed; + public static int errorCode; - public DanaRS_Packet_Bolus_Set_Step_Bolus_Start() { + public DanaRS_Packet_Bolus_Set_Step_Bolus_Start() { super(); opCode = BleCommandUtil.DANAR_PACKET__OPCODE_BOLUS__SET_STEP_BOLUS_START; } // Speed 0 => 12 sec/U, 1 => 30 sec/U, 2 => 60 sec/U - public DanaRS_Packet_Bolus_Set_Step_Bolus_Start(double amount, int speed) { - this(); + public DanaRS_Packet_Bolus_Set_Step_Bolus_Start(double amount, int speed) { + this(); - // HARDCODED LIMIT - amount = MainApp.getConfigBuilder().applyBolusConstraints(amount); - if (amount < 0) amount = 0d; - if (amount > HardLimits.maxBolus()) amount = HardLimits.maxBolus(); + // HARDCODED LIMIT + amount = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(amount)).value(); - this.amount = amount; - this.speed = speed; + this.amount = amount; + this.speed = speed; - if (Config.logDanaMessageDetail) - log.debug("Bolus start : " + amount + " speed: " + speed); - } + if (Config.logDanaMessageDetail) + log.debug("Bolus start : " + amount + " speed: " + speed); + } - @Override - public byte[] getRequestParams() { - int stepBolusRate = (int) (amount * 100); - byte[] request = new byte[3]; - request[0] = (byte) (stepBolusRate & 0xff); - request[1] = (byte) ((stepBolusRate >>> 8) & 0xff); - request[2] = (byte) (speed & 0xff); - return request; - } - @Override - public void handleMessage(byte[] data) { - int result = intFromBuff(data, 0, 1); - if (Config.logDanaMessageDetail) { - if (result == 0) - log.debug("Result OK"); - else - log.error("Result Error: " + result); - } - } + @Override + public byte[] getRequestParams() { + int stepBolusRate = (int) (amount * 100); + byte[] request = new byte[3]; + request[0] = (byte) (stepBolusRate & 0xff); + request[1] = (byte) ((stepBolusRate >>> 8) & 0xff); + request[2] = (byte) (speed & 0xff); + return request; + } - @Override - public String getFriendlyName() { - return "BOLUS__SET_STEP_BOLUS_START"; - } + @Override + public void handleMessage(byte[] data) { + errorCode = intFromBuff(data, 0, 1); + if (Config.logDanaMessageDetail) { + if (errorCode == 0) + log.debug("Result OK"); + else { + failed = true; + log.error("Result Error: " + errorCode); + } + } + } + + @Override + public String getFriendlyName() { + return "BOLUS__SET_STEP_BOLUS_START"; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_Bolus_Set_Step_Bolus_Stop.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_Bolus_Set_Step_Bolus_Stop.java index 5383fca413..83e84e28cc 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_Bolus_Set_Step_Bolus_Stop.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_Bolus_Set_Step_Bolus_Stop.java @@ -9,7 +9,7 @@ import info.nightscout.androidaps.R; import com.cozmo.danar.util.BleCommandUtil; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress; public class DanaRS_Packet_Bolus_Set_Step_Bolus_Stop extends DanaRS_Packet { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_Notify_Delivery_Complete.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_Notify_Delivery_Complete.java index 0139b12197..fb5793995c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_Notify_Delivery_Complete.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_Notify_Delivery_Complete.java @@ -9,7 +9,7 @@ import info.nightscout.androidaps.R; import com.cozmo.danar.util.BleCommandUtil; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress; public class DanaRS_Packet_Notify_Delivery_Complete extends DanaRS_Packet { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_Notify_Delivery_Rate_Display.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_Notify_Delivery_Rate_Display.java index 26c12c27af..5354836375 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_Notify_Delivery_Rate_Display.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_Notify_Delivery_Rate_Display.java @@ -7,7 +7,7 @@ import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import com.cozmo.danar.util.BleCommandUtil; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress; public class DanaRS_Packet_Notify_Delivery_Rate_Display extends DanaRS_Packet { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/BLEComm.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/BLEComm.java index a892404929..2901c4b863 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/BLEComm.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/BLEComm.java @@ -278,16 +278,17 @@ public class BLEComm { } public void writeCharacteristic_NO_RESPONSE(final BluetoothGattCharacteristic characteristic, final byte[] data) { - if ((mBluetoothAdapter == null) || (mBluetoothGatt == null)) { - log.debug("BluetoothAdapter not initialized_ERROR"); - isConnecting = false; - isConnected = false; - return; - } - new Thread(new Runnable() { public void run() { SystemClock.sleep(WRITE_DELAY_MILLIS); + + if ((mBluetoothAdapter == null) || (mBluetoothGatt == null)) { + log.debug("BluetoothAdapter not initialized_ERROR"); + isConnecting = false; + isConnected = false; + return; + } + characteristic.setValue(data); characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); log.debug("writeCharacteristic:" + DanaRS_Packet.toHexString(data)); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/DanaRSService.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/DanaRSService.java index 1a1f74b645..91859c759a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/DanaRSService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/DanaRSService.java @@ -19,7 +19,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventInitializationChanged; import info.nightscout.androidaps.events.EventPumpStatusChanged; @@ -71,8 +71,10 @@ import info.nightscout.androidaps.plugins.PumpDanaRS.comm.DanaRS_Packet_Notify_D import info.nightscout.androidaps.plugins.PumpDanaRS.comm.DanaRS_Packet_Option_Get_Pump_Time; import info.nightscout.androidaps.plugins.PumpDanaRS.comm.DanaRS_Packet_Option_Set_Pump_Time; import info.nightscout.androidaps.queue.Callback; +import info.nightscout.utils.DateUtil; import info.nightscout.utils.NSUpload; import info.nightscout.utils.SP; +import info.nightscout.utils.T; public class DanaRSService extends Service { private static Logger log = LoggerFactory.getLogger(DanaRSService.class); @@ -145,8 +147,10 @@ public class DanaRSService extends Service { bleComm.sendMessage(new DanaRS_Packet_Option_Get_Pump_Time()); long timeDiff = (danaRPump.pumpTime.getTime() - System.currentTimeMillis()) / 1000L; log.debug("Pump time difference: " + timeDiff + " seconds"); - if (Math.abs(timeDiff) > 10) { - bleComm.sendMessage(new DanaRS_Packet_Option_Set_Pump_Time(new Date())); + if (Math.abs(timeDiff) > 3) { + waitForWholeMinute(); // Dana can set only whole minute + // add 10sec to be sure we are over minute (will be cutted off anyway) + bleComm.sendMessage(new DanaRS_Packet_Option_Set_Pump_Time(new Date(DateUtil.now() + T.secs(10).msecs()))); bleComm.sendMessage(new DanaRS_Packet_Option_Get_Pump_Time()); timeDiff = (danaRPump.pumpTime.getTime() - System.currentTimeMillis()) / 1000L; log.debug("Pump time difference: " + timeDiff + " seconds"); @@ -156,7 +160,6 @@ public class DanaRSService extends Service { loadEvents(); - danaRPump.lastConnection = now; MainApp.bus().post(new EventDanaRNewStatus()); MainApp.bus().post(new EventInitializationChanged()); NSUpload.uploadDeviceStatus(); @@ -190,6 +193,7 @@ public class DanaRSService extends Service { else lastHistoryFetched = 0; log.debug("Events loaded"); + danaRPump.lastConnection = System.currentTimeMillis(); return new PumpEnactResult().success(true); } @@ -269,7 +273,7 @@ public class DanaRSService extends Service { MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.disconnecting))); } }); - return true; + return !start.failed; } public void bolusStop() { @@ -318,6 +322,25 @@ public class DanaRSService extends Service { return true; } + public boolean tempBasalShortDuration(Integer percent, int durationInMinutes) { + if (durationInMinutes != 15 && durationInMinutes != 30) { + log.error("Wrong duration param"); + return false; + } + + if (danaRPump.isTempBasalInProgress) { + MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); + bleComm.sendMessage(new DanaRS_Packet_Basal_Set_Cancel_Temporary_Basal()); + SystemClock.sleep(500); + } + MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.settingtempbasal))); + bleComm.sendMessage(new DanaRS_Packet_APS_Basal_Set_Temporary_Basal(percent, durationInMinutes == 15, durationInMinutes == 30)); + bleComm.sendMessage(new DanaRS_Packet_Basal_Get_Temporary_Basal_State()); + loadEvents(); + MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + return true; + } + public boolean tempBasalStop() { if (!isConnected()) return false; MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.stoppingtempbasal))); @@ -439,4 +462,14 @@ public class DanaRSService extends Service { log.debug("EventAppExit finished"); } + void waitForWholeMinute() { + while (true) { + long time = DateUtil.now(); + long timeToWholeMinute = (60000 - time % 60000); + if (timeToWholeMinute > 59800 || timeToWholeMinute < 300) + break; + MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.waitingfortimesynchronization, (int)(timeToWholeMinute / 1000)))); + SystemClock.sleep(Math.min(timeToWholeMinute, 100)); + } + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/DanaRv2Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/DanaRv2Plugin.java index 34a5b2381b..0ea6f192e4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/DanaRv2Plugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/DanaRv2Plugin.java @@ -14,15 +14,19 @@ import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.TemporaryBasal; -import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventAppExit; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.PumpDescription; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.ConfigBuilder.DetailedBolusInfoStorage; import info.nightscout.androidaps.plugins.PumpDanaR.AbstractDanaRPlugin; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgBolusStartWithSpeed; import info.nightscout.androidaps.plugins.PumpDanaRv2.services.DanaRv2ExecutionService; +import info.nightscout.androidaps.plugins.Treatments.Treatment; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; +import info.nightscout.utils.DateUtil; import info.nightscout.utils.Round; import info.nightscout.utils.SP; @@ -43,11 +47,6 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { log = LoggerFactory.getLogger(DanaRv2Plugin.class); useExtendedBoluses = false; - Context context = MainApp.instance().getApplicationContext(); - Intent intent = new Intent(context, DanaRv2ExecutionService.class); - context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); - MainApp.bus().register(this); - pumpDescription.isBolusCapable = true; pumpDescription.bolusStep = 0.05d; @@ -63,6 +62,8 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { pumpDescription.tempPercentStep = 10; pumpDescription.tempDurationStep = 60; + pumpDescription.tempDurationStep15mAllowed = true; + pumpDescription.tempDurationStep30mAllowed = true; pumpDescription.tempMaxDuration = 24 * 60; @@ -73,6 +74,27 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { pumpDescription.isRefillingCapable = true; pumpDescription.storesCarbInfo = true; + + pumpDescription.supportsTDDs = true; + pumpDescription.needsManualTDDLoad = true; + } + + @Override + protected void onStart() { + Context context = MainApp.instance().getApplicationContext(); + Intent intent = new Intent(context, DanaRv2ExecutionService.class); + context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + + MainApp.bus().register(this); + super.onStart(); + } + + @Override + protected void onStop() { + Context context = MainApp.instance().getApplicationContext(); + context.unbindService(mConnection); + + MainApp.bus().unregister(this); } private ServiceConnection mConnection = new ServiceConnection() { @@ -98,7 +120,7 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { // Plugin base interface @Override public String getName() { - return MainApp.instance().getString(R.string.danarv2pump); + return MainApp.gs(R.string.danarv2pump); } @Override @@ -119,8 +141,7 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { // Pump interface @Override public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - detailedBolusInfo.insulin = configBuilderPlugin.applyBolusConstraints(detailedBolusInfo.insulin); + detailedBolusInfo.insulin = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(detailedBolusInfo.insulin)).value(); if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) { // v2 stores end time for bolus, we need to adjust time // default delivery speed is 12 sec/U @@ -137,7 +158,7 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { speed = 60; break; } - detailedBolusInfo.date += speed * detailedBolusInfo.insulin * 1000; + detailedBolusInfo.date = DateUtil.now() + (long)(speed * detailedBolusInfo.insulin * 1000); // clean carbs to prevent counting them as twice because they will picked up as another record // I don't think it's necessary to copy DetailedBolusInfo right now for carbs records double carbs = detailedBolusInfo.carbs; @@ -148,14 +169,18 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { DetailedBolusInfoStorage.add(detailedBolusInfo); // will be picked up on reading history Treatment t = new Treatment(); + t.isSMB = detailedBolusInfo.isSMB; boolean connectionOK = false; if (detailedBolusInfo.insulin > 0 || carbs > 0) - connectionOK = sExecutionService.bolus(detailedBolusInfo.insulin, (int) carbs, System.currentTimeMillis() + carbTime * 60 * 1000 + 30000, t); // +30s to make the record different + connectionOK = sExecutionService.bolus(detailedBolusInfo.insulin, (int) carbs, DateUtil.now() + carbTime * 60 * 1000, t); PumpEnactResult result = new PumpEnactResult(); - result.success = connectionOK; + result.success = connectionOK && detailedBolusInfo.insulin == t.insulin; result.bolusDelivered = t.insulin; result.carbsDelivered = detailedBolusInfo.carbs; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + if (!result.success) + result.comment = String.format(MainApp.gs(R.string.boluserrorcode), detailedBolusInfo.insulin, t.insulin, MsgBolusStartWithSpeed.errorCode); + else + result.comment = MainApp.gs(R.string.virtualpump_resultok); if (Config.logPumpActions) log.debug("deliverTreatment: OK. Asked: " + detailedBolusInfo.insulin + " Delivered: " + result.bolusDelivered); // remove carbs because it's get from history seprately @@ -165,7 +190,7 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { result.success = false; result.bolusDelivered = 0d; result.carbsDelivered = 0d; - result.comment = MainApp.instance().getString(R.string.danar_invalidinput); + result.comment = MainApp.gs(R.string.danar_invalidinput); log.error("deliverTreatment: Invalid input"); return result; } @@ -182,7 +207,7 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { // This is called from APS @Override - public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean enforceNew) { + public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, Profile profile, boolean enforceNew) { // Recheck pump status if older than 30 min //This should not be needed while using queue because connection should be done before calling this //if (pump.lastConnection.getTime() + 30 * 60 * 1000L < System.currentTimeMillis()) { @@ -191,8 +216,7 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { PumpEnactResult result = new PumpEnactResult(); - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - absoluteRate = configBuilderPlugin.applyBasalConstraints(absoluteRate); + absoluteRate = MainApp.getConstraintChecker().applyBasalConstraints(new Constraint<>(absoluteRate), profile).value(); final boolean doTempOff = getBaseBasalRate() - absoluteRate == 0d; final boolean doLowTemp = absoluteRate < getBaseBasalRate(); @@ -200,7 +224,7 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { if (doTempOff) { // If temp in progress - if (MainApp.getConfigBuilder().isTempBasalInProgress()) { + if (TreatmentsPlugin.getPlugin().isTempBasalInProgress()) { if (Config.logPumpActions) log.debug("setTempBasalAbsolute: Stopping temp basal (doTempOff)"); return cancelTempBasal(false); @@ -222,15 +246,15 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { if (percentRate > 500) // Special high temp 500/15min percentRate = 500; // Check if some temp is already in progress - if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) { + TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); + if (activeTemp != null) { // Correct basal already set ? - if (MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()).percentRate == percentRate) { + if (activeTemp.percentRate == percentRate) { if (!enforceNew) { result.success = true; result.percent = percentRate; - result.absolute = MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory(); result.enacted = false; - result.duration = ((Double) MainApp.getConfigBuilder().getTempBasalRemainingMinutesFromHistory()).intValue(); + result.duration = activeTemp.getPlannedRemainingMinutes(); result.isPercent = true; result.isTempCancel = false; if (Config.logPumpActions) @@ -260,44 +284,47 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { } @Override - public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, boolean enforceNew) { + public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, Profile profile, boolean enforceNew) { PumpEnactResult result = new PumpEnactResult(); - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - percent = configBuilderPlugin.applyBasalConstraints(percent); + percent = MainApp.getConstraintChecker().applyBasalPercentConstraints(new Constraint<>(percent), profile).value(); if (percent < 0) { result.isTempCancel = false; result.enacted = false; result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_invalidinput); + result.comment = MainApp.gs(R.string.danar_invalidinput); log.error("setTempBasalPercent: Invalid input"); return result; } if (percent > getPumpDescription().maxTempPercent) percent = getPumpDescription().maxTempPercent; - TemporaryBasal runningTB = MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()); + long now = System.currentTimeMillis(); + TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getRealTempBasalFromHistory(now); if (runningTB != null && runningTB.percentRate == percent && !enforceNew) { result.enacted = false; result.success = true; result.isTempCancel = false; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.comment = MainApp.gs(R.string.virtualpump_resultok); result.duration = pump.tempBasalRemainingMin; result.percent = pump.tempBasalPercent; - result.absolute = MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory(); result.isPercent = true; if (Config.logPumpActions) log.debug("setTempBasalPercent: Correct value already set"); return result; } - int durationInHours = Math.max(durationInMinutes / 60, 1); - boolean connectionOK = sExecutionService.tempBasal(percent, durationInHours); + boolean connectionOK; + if (durationInMinutes == 15 || durationInMinutes == 30) { + connectionOK = sExecutionService.tempBasalShortDuration(percent, durationInMinutes); + } else { + int durationInHours = Math.max(durationInMinutes / 60, 1); + connectionOK = sExecutionService.tempBasal(percent, durationInHours); + } if (connectionOK && pump.isTempBasalInProgress && pump.tempBasalPercent == percent) { result.enacted = true; result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.comment = MainApp.gs(R.string.virtualpump_resultok); result.isTempCancel = false; result.duration = pump.tempBasalRemainingMin; result.percent = pump.tempBasalPercent; - result.absolute = MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory(); result.isPercent = true; if (Config.logPumpActions) log.debug("setTempBasalPercent: OK"); @@ -305,7 +332,7 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { } result.enacted = false; result.success = false; - result.comment = MainApp.instance().getString(R.string.tempbasaldeliveryerror); + result.comment = MainApp.gs(R.string.tempbasaldeliveryerror); log.error("setTempBasalPercent: Failed to set temp basal"); return result; } @@ -316,7 +343,7 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { if (connectionOK && pump.isTempBasalInProgress && pump.tempBasalPercent == percent) { result.enacted = true; result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.comment = MainApp.gs(R.string.virtualpump_resultok); result.isTempCancel = false; result.duration = pump.tempBasalRemainingMin; result.percent = pump.tempBasalPercent; @@ -327,7 +354,7 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { } result.enacted = false; result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_valuenotsetproperly); + result.comment = MainApp.gs(R.string.danar_valuenotsetproperly); log.error("setHighTempBasalPercent: Failed to set temp basal"); return result; } @@ -335,7 +362,7 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { @Override public PumpEnactResult cancelTempBasal(boolean force) { PumpEnactResult result = new PumpEnactResult(); - TemporaryBasal runningTB = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); + TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); if (runningTB != null) { sExecutionService.tempBasalStop(); result.enacted = true; @@ -344,13 +371,13 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { if (!pump.isTempBasalInProgress) { result.success = true; result.isTempCancel = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.comment = MainApp.gs(R.string.virtualpump_resultok); if (Config.logPumpActions) log.debug("cancelRealTempBasal: OK"); return result; } else { result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_valuenotsetproperly); + result.comment = MainApp.gs(R.string.danar_valuenotsetproperly); result.isTempCancel = true; log.error("cancelRealTempBasal: Failed to cancel temp basal"); return result; @@ -361,4 +388,5 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin { public PumpEnactResult loadEvents() { return sExecutionService.loadEvents(); } + } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgCheckValue_v2.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgCheckValue_v2.java index 60133d8d25..9117ad1c98 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgCheckValue_v2.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgCheckValue_v2.java @@ -3,16 +3,14 @@ package info.nightscout.androidaps.plugins.PumpDanaRv2.comm; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Date; - import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.events.EventRefreshGui; -import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPlugin; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MessageBase; @@ -41,23 +39,24 @@ public class MsgCheckValue_v2 extends MessageBase { pump.productCode = intFromBuff(bytes, 2, 1); if (pump.model != DanaRPump.EXPORT_MODEL) { pump.lastConnection = 0; - Notification notification = new Notification(Notification.WRONG_DRIVER, MainApp.sResources.getString(R.string.pumpdrivercorrected), Notification.NORMAL); + Notification notification = new Notification(Notification.WRONG_DRIVER, MainApp.sResources.getString(R.string.pumpdrivercorrected), Notification.NORMAL); MainApp.bus().post(new EventNewNotification(notification)); MainApp.getSpecificPlugin(DanaRPlugin.class).disconnect("Wrong Model"); log.debug("Wrong model selected. Switching to Korean DanaR"); - MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setFragmentEnabled(PluginBase.PUMP, true); - MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setFragmentVisible(PluginBase.PUMP, true); - MainApp.getSpecificPlugin(DanaRPlugin.class).setFragmentEnabled(PluginBase.PUMP, false); - MainApp.getSpecificPlugin(DanaRPlugin.class).setFragmentVisible(PluginBase.PUMP, false); - DanaRPump.getInstance().lastConnection = 0; // mark not initialized + MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setPluginEnabled(PluginType.PUMP, true); + MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setFragmentVisible(PluginType.PUMP, true); + MainApp.getSpecificPlugin(DanaRPlugin.class).setPluginEnabled(PluginType.PUMP, false); + MainApp.getSpecificPlugin(DanaRPlugin.class).setFragmentVisible(PluginType.PUMP, false); + + DanaRPump.reset(); // mark not initialized //If profile coming from pump, switch it as well - if(MainApp.getSpecificPlugin(DanaRPlugin.class).isEnabled(PluginBase.PROFILE)){ - (MainApp.getSpecificPlugin(DanaRPlugin.class)).setFragmentEnabled(PluginBase.PROFILE, false); - (MainApp.getSpecificPlugin(DanaRKoreanPlugin.class)).setFragmentEnabled(PluginBase.PROFILE, true); + if (MainApp.getSpecificPlugin(DanaRPlugin.class).isEnabled(PluginType.PROFILE)) { + (MainApp.getSpecificPlugin(DanaRPlugin.class)).setPluginEnabled(PluginType.PROFILE, false); + (MainApp.getSpecificPlugin(DanaRKoreanPlugin.class)).setPluginEnabled(PluginType.PROFILE, true); } - MainApp.getConfigBuilder().storeSettings(); + MainApp.getConfigBuilder().storeSettings("ChangingDanaRv2Driver"); MainApp.bus().post(new EventRefreshGui()); ConfigBuilderPlugin.getCommandQueue().readStatus("PumpDriverChange", null); // force new connection return; @@ -65,22 +64,22 @@ public class MsgCheckValue_v2 extends MessageBase { if (pump.protocol != 2) { pump.lastConnection = 0; - Notification notification = new Notification(Notification.WRONG_DRIVER, MainApp.sResources.getString(R.string.pumpdrivercorrected), Notification.NORMAL); + Notification notification = new Notification(Notification.WRONG_DRIVER, MainApp.sResources.getString(R.string.pumpdrivercorrected), Notification.NORMAL); MainApp.bus().post(new EventNewNotification(notification)); DanaRKoreanPlugin.getPlugin().disconnect("Wrong Model"); log.debug("Wrong model selected. Switching to non APS DanaR"); - (MainApp.getSpecificPlugin(DanaRv2Plugin.class)).setFragmentEnabled(PluginBase.PUMP, false); - (MainApp.getSpecificPlugin(DanaRv2Plugin.class)).setFragmentVisible(PluginBase.PUMP, false); - (MainApp.getSpecificPlugin(DanaRPlugin.class)).setFragmentEnabled(PluginBase.PUMP, true); - (MainApp.getSpecificPlugin(DanaRPlugin.class)).setFragmentVisible(PluginBase.PUMP, true); + (MainApp.getSpecificPlugin(DanaRv2Plugin.class)).setPluginEnabled(PluginType.PUMP, false); + (MainApp.getSpecificPlugin(DanaRv2Plugin.class)).setFragmentVisible(PluginType.PUMP, false); + (MainApp.getSpecificPlugin(DanaRPlugin.class)).setPluginEnabled(PluginType.PUMP, true); + (MainApp.getSpecificPlugin(DanaRPlugin.class)).setFragmentVisible(PluginType.PUMP, true); //If profile coming from pump, switch it as well - if(MainApp.getSpecificPlugin(DanaRv2Plugin.class).isEnabled(PluginBase.PROFILE)){ - (MainApp.getSpecificPlugin(DanaRv2Plugin.class)).setFragmentEnabled(PluginBase.PROFILE, false); - (MainApp.getSpecificPlugin(DanaRPlugin.class)).setFragmentEnabled(PluginBase.PROFILE, true); + if (MainApp.getSpecificPlugin(DanaRv2Plugin.class).isEnabled(PluginType.PROFILE)) { + (MainApp.getSpecificPlugin(DanaRv2Plugin.class)).setPluginEnabled(PluginType.PROFILE, false); + (MainApp.getSpecificPlugin(DanaRPlugin.class)).setPluginEnabled(PluginType.PROFILE, true); } - MainApp.getConfigBuilder().storeSettings(); + MainApp.getConfigBuilder().storeSettings("ChangingDanaRv2Driver"); MainApp.bus().post(new EventRefreshGui()); ConfigBuilderPlugin.getCommandQueue().readStatus("PumpDriverChange", null); // force new connection return; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgHistoryEvents_v2.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgHistoryEvents_v2.java index 922047e5cb..ac31d2df92 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgHistoryEvents_v2.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgHistoryEvents_v2.java @@ -16,6 +16,7 @@ import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.plugins.ConfigBuilder.DetailedBolusInfoStorage; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MessageBase; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.DateUtil; public class MsgHistoryEvents_v2 extends MessageBase { @@ -56,10 +57,10 @@ public class MsgHistoryEvents_v2 extends MessageBase { int param1 = intFromBuff(bytes, 7, 2); int param2 = intFromBuff(bytes, 9, 2); - TemporaryBasal temporaryBasal = new TemporaryBasal(); - temporaryBasal.date = datetime.getTime(); - temporaryBasal.source = Source.PUMP; - temporaryBasal.pumpId = datetime.getTime(); + TemporaryBasal temporaryBasal = new TemporaryBasal() + .date(datetime.getTime()) + .source(Source.PUMP) + .pumpId(datetime.getTime()); ExtendedBolus extendedBolus = new ExtendedBolus(); extendedBolus.date = datetime.getTime(); @@ -84,36 +85,36 @@ public class MsgHistoryEvents_v2 extends MessageBase { log.debug("EVENT TEMPSTART (" + recordCode + ") " + datetime.toLocaleString() + " Ratio: " + param1 + "% Duration: " + param2 + "min"); temporaryBasal.percentRate = param1; temporaryBasal.durationInMinutes = param2; - MainApp.getConfigBuilder().addToHistoryTempBasal(temporaryBasal); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(temporaryBasal); status = "TEMPSTART " + DateUtil.timeString(datetime); break; case DanaRPump.TEMPSTOP: log.debug("EVENT TEMPSTOP (" + recordCode + ") " + datetime.toLocaleString()); - MainApp.getConfigBuilder().addToHistoryTempBasal(temporaryBasal); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(temporaryBasal); status = "TEMPSTOP " + DateUtil.timeString(datetime); break; case DanaRPump.EXTENDEDSTART: log.debug("EVENT EXTENDEDSTART (" + recordCode + ") " + datetime.toLocaleString() + " Amount: " + (param1 / 100d) + "U Duration: " + param2 + "min"); extendedBolus.insulin = param1 / 100d; extendedBolus.durationInMinutes = param2; - MainApp.getConfigBuilder().addToHistoryExtendedBolus(extendedBolus); + TreatmentsPlugin.getPlugin().addToHistoryExtendedBolus(extendedBolus); status = "EXTENDEDSTART " + DateUtil.timeString(datetime); break; case DanaRPump.EXTENDEDSTOP: log.debug("EVENT EXTENDEDSTOP (" + recordCode + ") " + datetime.toLocaleString() + " Delivered: " + (param1 / 100d) + "U RealDuration: " + param2 + "min"); - MainApp.getConfigBuilder().addToHistoryExtendedBolus(extendedBolus); + TreatmentsPlugin.getPlugin().addToHistoryExtendedBolus(extendedBolus); status = "EXTENDEDSTOP " + DateUtil.timeString(datetime); break; case DanaRPump.BOLUS: detailedBolusInfo.insulin = param1 / 100d; - boolean newRecord = MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); + boolean newRecord = TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo); log.debug((newRecord ? "**NEW** " : "") + "EVENT BOLUS (" + recordCode + ") " + datetime.toLocaleString() + " Bolus: " + (param1 / 100d) + "U Duration: " + param2 + "min"); DetailedBolusInfoStorage.remove(detailedBolusInfo.date); status = "BOLUS " + DateUtil.timeString(datetime); break; case DanaRPump.DUALBOLUS: detailedBolusInfo.insulin = param1 / 100d; - newRecord = MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); + newRecord = TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo); log.debug((newRecord ? "**NEW** " : "") + "EVENT DUALBOLUS (" + recordCode + ") " + datetime.toLocaleString() + " Bolus: " + (param1 / 100d) + "U Duration: " + param2 + "min"); DetailedBolusInfoStorage.remove(detailedBolusInfo.date); status = "DUALBOLUS " + DateUtil.timeString(datetime); @@ -122,12 +123,12 @@ public class MsgHistoryEvents_v2 extends MessageBase { log.debug("EVENT DUALEXTENDEDSTART (" + recordCode + ") " + datetime.toLocaleString() + " Amount: " + (param1 / 100d) + "U Duration: " + param2 + "min"); extendedBolus.insulin = param1 / 100d; extendedBolus.durationInMinutes = param2; - MainApp.getConfigBuilder().addToHistoryExtendedBolus(extendedBolus); + TreatmentsPlugin.getPlugin().addToHistoryExtendedBolus(extendedBolus); status = "DUALEXTENDEDSTART " + DateUtil.timeString(datetime); break; case DanaRPump.DUALEXTENDEDSTOP: log.debug("EVENT DUALEXTENDEDSTOP (" + recordCode + ") " + datetime.toLocaleString() + " Delivered: " + (param1 / 100d) + "U RealDuration: " + param2 + "min"); - MainApp.getConfigBuilder().addToHistoryExtendedBolus(extendedBolus); + TreatmentsPlugin.getPlugin().addToHistoryExtendedBolus(extendedBolus); status = "DUALEXTENDEDSTOP " + DateUtil.timeString(datetime); break; case DanaRPump.SUSPENDON: @@ -156,7 +157,7 @@ public class MsgHistoryEvents_v2 extends MessageBase { emptyCarbsInfo.date = datetime.getTime(); emptyCarbsInfo.source = Source.PUMP; emptyCarbsInfo.pumpId = datetime.getTime(); - newRecord = MainApp.getConfigBuilder().addToHistoryTreatment(emptyCarbsInfo); + newRecord = TreatmentsPlugin.getPlugin().addToHistoryTreatment(emptyCarbsInfo); log.debug((newRecord ? "**NEW** " : "") + "EVENT CARBS (" + recordCode + ") " + datetime.toLocaleString() + " Carbs: " + param1 + "g"); status = "CARBS " + DateUtil.timeString(datetime); break; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgSetAPSTempBasalStart_v2.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgSetAPSTempBasalStart_v2.java index da4db61362..6b6e3045de 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgSetAPSTempBasalStart_v2.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgSetAPSTempBasalStart_v2.java @@ -9,28 +9,55 @@ import info.nightscout.androidaps.plugins.PumpDanaR.comm.MessageBase; public class MsgSetAPSTempBasalStart_v2 extends MessageBase { private static Logger log = LoggerFactory.getLogger(MsgSetAPSTempBasalStart_v2.class); + protected final int PARAM30MIN = 160; + protected final int PARAM15MIN = 150; + public MsgSetAPSTempBasalStart_v2() { SetCommand(0xE002); } public MsgSetAPSTempBasalStart_v2(int percent) { this(); + setParams(percent); + } + protected void setParams(int percent) { //HARDCODED LIMITS if (percent < 0) percent = 0; if (percent > 500) percent = 500; AddParamInt(percent); if (percent < 100) { - AddParamByte((byte) 0xA0); // 160 + AddParamByte((byte) PARAM30MIN); if (Config.logDanaMessageDetail) log.debug("APS Temp basal start percent: " + percent + " duration 30 min"); } else { - AddParamByte((byte) 0x96); // 150 + AddParamByte((byte) PARAM15MIN); if (Config.logDanaMessageDetail) log.debug("APS Temp basal start percent: " + percent + " duration 15 min"); } + } + public MsgSetAPSTempBasalStart_v2(int percent, boolean fifteenMinutes, boolean thirtyMinutes) { + this(); + setParams(percent, fifteenMinutes, thirtyMinutes); + } + + protected void setParams(int percent, boolean fifteenMinutes, boolean thirtyMinutes) { + //HARDCODED LIMITS + if (percent < 0) percent = 0; + if (percent > 500) percent = 500; + + AddParamInt(percent); + if (thirtyMinutes && percent <= 200) { // 30 min is allowed up to 200% + AddParamByte((byte) PARAM30MIN); + if (Config.logDanaMessageDetail) + log.debug("APS Temp basal start percent: " + percent + " duration 30 min"); + } else { + AddParamByte((byte) PARAM15MIN); + if (Config.logDanaMessageDetail) + log.debug("APS Temp basal start percent: " + percent + " duration 15 min"); + } } public void handleMessage(byte[] bytes) { @@ -39,6 +66,7 @@ public class MsgSetAPSTempBasalStart_v2 extends MessageBase { failed = true; log.debug("Set APS temp basal start result: " + result + " FAILED!!!"); } else { + failed = false; if (Config.logDanaMessageDetail) log.debug("Set APS temp basal start result: " + result); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/services/DanaRv2ExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/services/DanaRv2ExecutionService.java index 81b5837511..25cac9da1c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/services/DanaRv2ExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/services/DanaRv2ExecutionService.java @@ -18,7 +18,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventInitializationChanged; import info.nightscout.androidaps.events.EventPreferenceChange; @@ -64,8 +64,10 @@ import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MsgSetHistoryEntry_v2 import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MsgStatusBolusExtended_v2; import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MsgStatusTempBasal_v2; import info.nightscout.androidaps.queue.Callback; +import info.nightscout.utils.DateUtil; import info.nightscout.utils.NSUpload; import info.nightscout.utils.SP; +import info.nightscout.utils.T; import info.nightscout.utils.ToastUtils; public class DanaRv2ExecutionService extends AbstractDanaRExecutionService { @@ -198,8 +200,10 @@ public class DanaRv2ExecutionService extends AbstractDanaRExecutionService { mSerialIOThread.sendMessage(new MsgSettingPumpTime()); long timeDiff = (mDanaRPump.pumpTime.getTime() - System.currentTimeMillis()) / 1000L; log.debug("Pump time difference: " + timeDiff + " seconds"); - if (Math.abs(timeDiff) > 10) { - mSerialIOThread.sendMessage(new MsgSetTime(new Date())); + if (Math.abs(timeDiff) > 3) { + waitForWholeMinute(); // Dana can set only whole minute + // add 10sec to be sure we are over minute (will be cutted off anyway) + mSerialIOThread.sendMessage(new MsgSetTime(new Date(DateUtil.now() + T.secs(10).msecs()))); mSerialIOThread.sendMessage(new MsgSettingPumpTime()); timeDiff = (mDanaRPump.pumpTime.getTime() - System.currentTimeMillis()) / 1000L; log.debug("Pump time difference: " + timeDiff + " seconds"); @@ -209,7 +213,6 @@ public class DanaRv2ExecutionService extends AbstractDanaRExecutionService { loadEvents(); - mDanaRPump.lastConnection = now; MainApp.bus().post(new EventDanaRNewStatus()); MainApp.bus().post(new EventInitializationChanged()); NSUpload.uploadDeviceStatus(); @@ -255,6 +258,26 @@ public class DanaRv2ExecutionService extends AbstractDanaRExecutionService { return true; } + public boolean tempBasalShortDuration(int percent, int durationInMinutes) { + if (durationInMinutes != 15 && durationInMinutes != 30) { + log.error("Wrong duration param"); + return false; + } + + if (!isConnected()) return false; + if (mDanaRPump.isTempBasalInProgress) { + MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); + mSerialIOThread.sendMessage(new MsgSetTempBasalStop()); + SystemClock.sleep(500); + } + MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.settingtempbasal))); + mSerialIOThread.sendMessage(new MsgSetAPSTempBasalStart_v2(percent, durationInMinutes == 15, durationInMinutes == 30)); + mSerialIOThread.sendMessage(new MsgStatusTempBasal_v2()); + loadEvents(); + MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + return true; + } + public boolean tempBasalStop() { if (!isConnected()) return false; MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.stoppingtempbasal))); @@ -363,7 +386,7 @@ public class DanaRv2ExecutionService extends AbstractDanaRExecutionService { MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.disconnecting))); } }); - return true; + return !start.failed; } public void bolusStop() { @@ -413,6 +436,7 @@ public class DanaRv2ExecutionService extends AbstractDanaRExecutionService { lastHistoryFetched = MsgHistoryEvents_v2.lastEventTimeLoaded - 45 * 60 * 1000L; //always load last 45 min; else lastHistoryFetched = 0; + mDanaRPump.lastConnection = System.currentTimeMillis(); return new PumpEnactResult().success(true); } @@ -430,4 +454,14 @@ public class DanaRv2ExecutionService extends AbstractDanaRExecutionService { return true; } + void waitForWholeMinute() { + while (true) { + long time = DateUtil.now(); + long timeToWholeMinute = (60000 - time % 60000); + if (timeToWholeMinute > 59800 || timeToWholeMinute < 3000) + break; + MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.waitingfortimesynchronization, (int)(timeToWholeMinute / 1000)))); + SystemClock.sleep(Math.min(timeToWholeMinute, 100)); + } + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/Cstatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/Cstatus.java new file mode 100644 index 0000000000..53c0cf798f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/Cstatus.java @@ -0,0 +1,20 @@ +package info.nightscout.androidaps.plugins.PumpInsight; + +/** + * Created by jamorham on 25/01/2018. + * + * Async command status + * + */ +enum Cstatus { + UNKNOWN, + PENDING, + SUCCESS, + FAILURE, + TIMEOUT; + + boolean success() { + return this == SUCCESS; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/InsightAsyncAdapter.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/InsightAsyncAdapter.java new file mode 100644 index 0000000000..3759a3f721 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/InsightAsyncAdapter.java @@ -0,0 +1,95 @@ +package info.nightscout.androidaps.plugins.PumpInsight; + +import android.os.PowerManager; + +import com.squareup.otto.Subscribe; + +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.plugins.PumpInsight.events.EventInsightCallback; + +import static info.nightscout.androidaps.plugins.PumpInsight.utils.Helpers.getWakeLock; +import static info.nightscout.androidaps.plugins.PumpInsight.utils.Helpers.msSince; +import static info.nightscout.androidaps.plugins.PumpInsight.utils.Helpers.releaseWakeLock; +import static info.nightscout.androidaps.plugins.PumpInsight.utils.Helpers.tsl; + +/** + * Created by jamorham on 25/01/2018. + * + * Asynchronous adapter + * + */ + +public class InsightAsyncAdapter { + + private final ConcurrentHashMap commandResults = new ConcurrentHashMap<>(); + + InsightAsyncAdapter() { + MainApp.bus().register(this); + } + + // just log during debugging + private static void log(String msg) { + android.util.Log.e("INSIGHTPUMPASYNC", msg); + } + + @Subscribe + public void onStatusEvent(final EventInsightCallback ev) { + log("Received callback event: " + ev.toString()); + commandResults.put(ev.request_uuid, ev); + } + + // poll command result + private Cstatus checkCommandResult(UUID uuid) { + if (uuid == null) return Cstatus.FAILURE; + if (commandResults.containsKey(uuid)) { + if (commandResults.get(uuid).success) { + return Cstatus.SUCCESS; + } else { + return Cstatus.FAILURE; + } + } else { + return Cstatus.PENDING; + } + } + + // blocking call to wait for result callback + private Cstatus busyWaitForCommandInternal(final UUID uuid, long wait_time) { + final PowerManager.WakeLock wl = getWakeLock("insight-wait-cmd", 60000); + try { + log("busy wait for command " + uuid); + if (uuid == null) return Cstatus.FAILURE; + final long start_time = tsl(); + Cstatus status = checkCommandResult(uuid); + while ((status == Cstatus.PENDING) && msSince(start_time) < wait_time) { + //log("command result waiting"); + try { + Thread.sleep(200); + } catch (InterruptedException e) { + log("Got interrupted exception! " + e); + } + status = checkCommandResult(uuid); + } + if (status == Cstatus.PENDING) { + return Cstatus.TIMEOUT; + } else { + return status; + } + } finally { + releaseWakeLock(wl); + } + } + + // wait for and then package result, cleanup and return + Mstatus busyWaitForCommandResult(final UUID uuid, long wait_time) { + final Mstatus mstatus = new Mstatus(); + mstatus.cstatus = busyWaitForCommandInternal(uuid, wait_time); + mstatus.event = commandResults.get(uuid); + commandResults.remove(uuid); + return mstatus; + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/InsightFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/InsightFragment.java new file mode 100644 index 0000000000..156c8271e3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/InsightFragment.java @@ -0,0 +1,114 @@ +package info.nightscout.androidaps.plugins.PumpInsight; + +import android.app.Activity; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + + +import com.squareup.otto.Subscribe; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.Common.SubscriberFragment; +import info.nightscout.androidaps.plugins.PumpInsight.events.EventInsightUpdateGui; +import info.nightscout.androidaps.plugins.PumpInsight.utils.StatusItem; +import info.nightscout.androidaps.plugins.PumpInsight.utils.ui.StatusItemViewAdapter; +import info.nightscout.utils.FabricPrivacy; + + +public class InsightFragment extends SubscriberFragment { + private static final Logger log = LoggerFactory.getLogger(InsightFragment.class); + private static final Handler sLoopHandler = new Handler(); + private static volatile boolean refresh = false; + private static volatile boolean pending = false; + StatusItemViewAdapter viewAdapter; + LinearLayout holder; + private final Runnable sRefreshLoop = new Runnable() { + @Override + public void run() { + pending = false; + updateGUI(); + if (refresh) { + scheduleRefresh(); + } + } + }; + + private synchronized void scheduleRefresh() { + if (!pending) { + pending = true; + sLoopHandler.postDelayed(sRefreshLoop, 30 * 1000L); + } + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + try { + final View view = inflater.inflate(R.layout.insightpump_fragment, container, false); + holder = (LinearLayout) view.findViewById(R.id.insightholder); + viewAdapter = new StatusItemViewAdapter(getActivity(), holder); + + return view; + } catch (Exception e) { + FabricPrivacy.logException(e); + } + + return null; + } + + + @Override + public void setUserVisibleHint(boolean visible) { + super.setUserVisibleHint(visible); + if (visible) { + refresh = true; + pending = false; + updateGUI(); + scheduleRefresh(); + } else { + refresh = false; + //sLoopHandler.removeCallbacksAndMessages(null); + } + } + + + @Subscribe + public void onStatusEvent(final EventInsightUpdateGui ev) { + updateGUI(); + } + + @Override + protected void updateGUI() { + final Activity activity = getActivity(); + if (activity != null && holder != null) + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + final InsightPlugin insightPlugin = InsightPlugin.getPlugin(); + final List l = insightPlugin.getStatusItems(refresh); + + holder.removeAllViews(); + + for (StatusItem row : l) { + viewAdapter.inflateStatus(row); + } + + } + }); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/InsightPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/InsightPlugin.java new file mode 100644 index 0000000000..c0e53ddaac --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/InsightPlugin.java @@ -0,0 +1,1042 @@ +package info.nightscout.androidaps.plugins.PumpInsight; + +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import info.nightscout.androidaps.BuildConfig; +import info.nightscout.androidaps.Config; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.db.ExtendedBolus; +import info.nightscout.androidaps.db.Source; +import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.plugins.Treatments.Treatment; +import info.nightscout.androidaps.interfaces.Constraint; +import info.nightscout.androidaps.interfaces.ConstraintsInterface; +import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.interfaces.PumpDescription; +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; +import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; +import info.nightscout.androidaps.plugins.PumpInsight.connector.CancelBolusTaskRunner; +import info.nightscout.androidaps.plugins.PumpInsight.connector.Connector; +import info.nightscout.androidaps.plugins.PumpInsight.connector.SetTBRTaskRunner; +import info.nightscout.androidaps.plugins.PumpInsight.connector.StatusTaskRunner; +import info.nightscout.androidaps.plugins.PumpInsight.connector.WriteBasalProfileTaskRunner; +import info.nightscout.androidaps.plugins.PumpInsight.events.EventInsightCallback; +import info.nightscout.androidaps.plugins.PumpInsight.events.EventInsightUpdateGui; +import info.nightscout.androidaps.plugins.PumpInsight.history.HistoryReceiver; +import info.nightscout.androidaps.plugins.PumpInsight.history.LiveHistory; +import info.nightscout.androidaps.plugins.PumpInsight.utils.Helpers; +import info.nightscout.androidaps.plugins.PumpInsight.utils.StatusItem; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.NSUpload; +import info.nightscout.utils.SP; +import sugar.free.sightparser.applayer.descriptors.ActiveBolus; +import sugar.free.sightparser.applayer.descriptors.ActiveBolusType; +import sugar.free.sightparser.applayer.descriptors.PumpStatus; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.BRProfileBlock; +import sugar.free.sightparser.applayer.messages.AppLayerMessage; +import sugar.free.sightparser.applayer.messages.remote_control.BolusMessage; +import sugar.free.sightparser.applayer.messages.remote_control.CancelBolusMessage; +import sugar.free.sightparser.applayer.messages.remote_control.CancelTBRMessage; +import sugar.free.sightparser.applayer.messages.remote_control.ExtendedBolusMessage; +import sugar.free.sightparser.applayer.messages.remote_control.StandardBolusMessage; +import sugar.free.sightparser.applayer.messages.status.ActiveBolusesMessage; +import sugar.free.sightparser.handling.SingleMessageTaskRunner; +import sugar.free.sightparser.handling.TaskRunner; +import sugar.free.sightparser.pipeline.Status; + +import static info.nightscout.androidaps.plugins.PumpInsight.history.PumpIdCache.getRecordUniqueID; + + +/** + * Created by jamorham on 23/01/2018. + *

+ * Connects to SightRemote app service using SightParser library + *

+ * SightRemote and SightParser created by Tebbe Ubben + *

+ * Original proof of concept SightProxy by jamorham + */ + +@SuppressWarnings("AccessStaticViaInstance") +public class InsightPlugin extends PluginBase implements PumpInterface, ConstraintsInterface { + + private static volatile InsightPlugin plugin; + + public static InsightPlugin getPlugin() { + if (plugin == null) { + plugin = new InsightPlugin(); + } + return plugin; + } + + private static final long BUSY_WAIT_TIME = 20000; + private static Integer batteryPercent = 0; + private static Integer reservoirInUnits = 0; + private static boolean initialized = false; + private static volatile boolean update_pending = false; + private static Logger log = LoggerFactory.getLogger(InsightPlugin.class); + private final InsightAsyncAdapter async = new InsightAsyncAdapter(); + private StatusTaskRunner.Result statusResult; + private long statusResultTime = -1; + private Date lastDataTime = new Date(0); + private boolean fauxTBRcancel = true; + private PumpDescription pumpDescription = new PumpDescription(); + private double basalRate = 0; + private Connector connector; + private volatile boolean connector_enabled = false; + private List profileBlocks; + + private InsightPlugin() { + super(new PluginDescription() + .mainType(PluginType.PUMP) + .fragmentClass(InsightFragment.class.getName()) + .pluginName(R.string.insightpump) + .shortName(R.string.insightpump_shortname) + .preferencesId(R.xml.pref_insightpump) + ); + log("InsightPlugin instantiated"); + pumpDescription.isBolusCapable = true; + pumpDescription.bolusStep = 0.05d; // specification says 0.05U up to 2U then 0.1U @ 2-5U 0.2U @ 10-20U 0.5U 10-20U (are these just UI restrictions?) + + pumpDescription.isExtendedBolusCapable = true; + pumpDescription.extendedBolusStep = 0.05d; // specification probably same as above + pumpDescription.extendedBolusDurationStep = 15; // 15 minutes up to 24 hours + pumpDescription.extendedBolusMaxDuration = 24 * 60; + + pumpDescription.isTempBasalCapable = true; + //pumpDescription.tempBasalStyle = PumpDescription.PERCENT | PumpDescription.ABSOLUTE; + pumpDescription.tempBasalStyle = PumpDescription.PERCENT; + + pumpDescription.maxTempPercent = 250; // 0-250% + pumpDescription.tempPercentStep = 10; + + pumpDescription.tempDurationStep = 15; // 15 minutes up to 24 hours + pumpDescription.tempDurationStep15mAllowed = true; + pumpDescription.tempDurationStep30mAllowed = true; + pumpDescription.tempMaxDuration = 24 * 60; + + pumpDescription.isSetBasalProfileCapable = true; + pumpDescription.is30minBasalRatesCapable = true; + pumpDescription.basalStep = 0.01d; + pumpDescription.basalMinimumRate = 0.02d; + + pumpDescription.isRefillingCapable = true; + + pumpDescription.storesCarbInfo = false; + + pumpDescription.supportsTDDs = true; + pumpDescription.needsManualTDDLoad = false; + } + + + // just log during debugging + private static void log(String msg) { + android.util.Log.e("INSIGHTPUMP", msg); + } + + private static void updateGui() { + update_pending = false; + MainApp.bus().post(new EventInsightUpdateGui()); + } + + private static void pushCallbackEvent(EventInsightCallback e) { + MainApp.bus().post(e); + } + + @Override + protected void onStart() { + if (!connector_enabled) { + synchronized (this) { + if (!connector_enabled) { + log("Instantiating connector"); + connector_enabled = true; + this.connector = Connector.get(); + this.connector.init(); + } + } + } + super.onStart(); + } + + protected void onStop() { + if (connector_enabled) { + synchronized (this) { + if (connector_enabled) { + log("Shutting down connector"); + Connector.get().shutdown(); + connector_enabled = false; + } + } + } + } + + @Override + public boolean isFakingTempsByExtendedBoluses() { + return false; + } + + @Override + public PumpEnactResult loadTDDs() { + PumpEnactResult result = new PumpEnactResult(); + result.success = true; + return result; + } + + @Override + public boolean isInitialized() { + return initialized; + } + + @Override + public boolean isSuspended() { + return !isPumpRunning(); + } + + @Override + public boolean isBusy() { + return false; + } + + @Override + public boolean isConnected() { + return Connector.get().isPumpConnected(); + } + + @Override + public boolean isConnecting() { + return Connector.get().isPumpConnecting(); + } + + @Override + public void connect(String reason) { + log("InsightPlugin::connect()"); + try { + if (!connector.isPumpConnected()) { + if (Helpers.ratelimit("insight-connect-timer", 40)) { + log("Actually requesting a connect"); + connector.connectToPump(); + } + } else { + log("Already connected"); + } + } catch (NullPointerException e) { + log("Could not sconnect - null pointer: " + e); + } + + // TODO review + if (!Config.NSCLIENT && !Config.G5UPLOADER) + NSUpload.uploadDeviceStatus(); + } + + @Override + public void disconnect(String reason) { + log("InsightPlugin::disconnect()"); + try { + if (!SP.getBoolean("insight_always_connected", false)) { + log("Requesting disconnect"); + connector.disconnectFromPump(); + } else { + log("Not disconnecting due to preference"); + } + } catch (NullPointerException e) { + log("Could not disconnect - null pointer: " + e); + } + } + + @Override + public void stopConnecting() { + log("InsightPlugin::stopConnecting()"); + try { + if (isConnecting()) { + if (!SP.getBoolean("insight_always_connected", false)) { + log("Requesting disconnect"); + connector.disconnectFromPump(); + } else { + log("Not disconnecting due to preference"); + } + } else { + log("Not currently trying to connect so not stopping connection"); + } + } catch (NullPointerException e) { + log("Could not stop connecting - null pointer: " + e); + } + } + + @Override + public void getPumpStatus() { + + log("getPumpStatus"); + if (Connector.get().isPumpConnected()) { + log("is connected.. requesting status"); + final UUID uuid = aSyncTaskRunner(new StatusTaskRunner(connector.getServiceConnector()), "Status"); + Mstatus mstatus = async.busyWaitForCommandResult(uuid, BUSY_WAIT_TIME); + if (mstatus.success()) { + log("GOT STATUS RESULT!!! PARTY WOOHOO!!!"); + setStatusResult((StatusTaskRunner.Result) mstatus.getResponseObject()); + statusResultTime = Helpers.tsl(); + processStatusResult(); + updateGui(); + connector.requestHistoryReSync(); + connector.requestHistorySync(); + } else { + log("StatusTaskRunner wasn't successful."); + if (connector.getServiceConnector().isConnectedToService() && connector.getServiceConnector().getStatus() != Status.CONNECTED) { + if (Helpers.ratelimit("insight-reconnect", 2)) { + Connector.connectToPump(); + updateGui(); + } + } + } + } else { + log("not connected.. not requesting status"); + } + } + + public void setStatusResult(StatusTaskRunner.Result result) { + this.statusResult = result; + } + + @Override + public PumpEnactResult setNewBasalProfile(Profile profile) { + PumpEnactResult result = new PumpEnactResult(); + if (!isInitialized()) { + log.error("setNewBasalProfile not initialized"); + Notification notification = new Notification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED, MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet), Notification.URGENT); + MainApp.bus().post(new EventNewNotification(notification)); + result.comment = MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet); + return result; + } + MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); + List profileBlocks = new ArrayList<>(); + for (int i = 0; i < profile.getBasalValues().length; i++) { + Profile.BasalValue basalValue = profile.getBasalValues()[i]; + Profile.BasalValue nextValue = null; + if (profile.getBasalValues().length > i + 1) + nextValue = profile.getBasalValues()[i + 1]; + profileBlocks.add(new BRProfileBlock.ProfileBlock((((nextValue != null ? nextValue.timeAsSeconds : 24 * 60 * 60) - basalValue.timeAsSeconds) / 60), Helpers.roundDouble(basalValue.value, 2))); + log("setNewBasalProfile: " + basalValue.value + " for " + Integer.toString(((nextValue != null ? nextValue.timeAsSeconds : 24 * 60 * 60) - basalValue.timeAsSeconds) / 60)); + } + final UUID uuid = aSyncTaskRunner(new WriteBasalProfileTaskRunner(connector.getServiceConnector(), profileBlocks), "Write basal profile"); + final Mstatus ms = async.busyWaitForCommandResult(uuid, BUSY_WAIT_TIME); + if (ms.success()) { + MainApp.bus().post(new EventDismissNotification(Notification.FAILED_UDPATE_PROFILE)); + Notification notification = new Notification(Notification.PROFILE_SET_OK, MainApp.sResources.getString(R.string.profile_set_ok), Notification.INFO, 60); + MainApp.bus().post(new EventNewNotification(notification)); + result.success = true; + result.enacted = true; + result.comment = "OK"; + this.profileBlocks = profileBlocks; + } else { + Notification notification = new Notification(Notification.FAILED_UDPATE_PROFILE, MainApp.sResources.getString(R.string.failedupdatebasalprofile), Notification.URGENT); + MainApp.bus().post(new EventNewNotification(notification)); + result.comment = MainApp.sResources.getString(R.string.failedupdatebasalprofile); + } + return result; + } + + @Override + public boolean isThisProfileSet(Profile profile) { + if (!isInitialized() || profileBlocks == null) return true; + if (profile.getBasalValues().length != profileBlocks.size()) return false; + for (int i = 0; i < profileBlocks.size(); i++) { + BRProfileBlock.ProfileBlock profileBlock = profileBlocks.get(i); + Profile.BasalValue basalValue = profile.getBasalValues()[i]; + Profile.BasalValue nextValue = null; + if (profile.getBasalValues().length > i + 1) + nextValue = profile.getBasalValues()[i + 1]; + log("isThisProfileSet - Comparing block: Pump: " + profileBlock.getAmount() + " for " + profileBlock.getDuration() + + " Profile: " + basalValue.value + " for " + Integer.toString(((nextValue != null ? nextValue.timeAsSeconds : 24 * 60 * 60) - basalValue.timeAsSeconds) / 60)); + if (profileBlock.getDuration() * 60 != (nextValue != null ? nextValue.timeAsSeconds : 24 * 60 * 60) - basalValue.timeAsSeconds) + return false; + //Allow a little imprecision due to rounding errors + if (Math.abs(profileBlock.getAmount() - Helpers.roundDouble(basalValue.value, 2)) >= 0.01D) + return false; + } + return true; + } + + @Override + public Date lastDataTime() { + return lastDataTime; + } + + @Override + public double getBaseBasalRate() { + return basalRate; + } + + public String getBaseBasalRateString() { + final DecimalFormat df = new DecimalFormat("#.##"); + return df.format(basalRate); + } + + @Override + public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { + final PumpEnactResult result = new PumpEnactResult(); + result.bolusDelivered = detailedBolusInfo.insulin; + result.carbsDelivered = detailedBolusInfo.carbs; + result.enacted = result.bolusDelivered > 0 || result.carbsDelivered > 0; + result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + + result.percent = 100; + + int bolusId = 0; + + // is there an insulin component to the treatment? + if (detailedBolusInfo.insulin > 0) { + final UUID cmd = deliverBolus(detailedBolusInfo.insulin); // actually request delivery + if (cmd == null) { + return pumpEnactFailure(); + } + final Mstatus ms = async.busyWaitForCommandResult(cmd, BUSY_WAIT_TIME); + + result.success = ms.success(); + if (ms.success()) { + detailedBolusInfo.pumpId = getRecordUniqueID(ms.getResponseID()); + bolusId = ms.getResponseID(); + } + } else { + result.success = true; // always true with carb only treatments + } + + if (result.success) { + log("Success!"); + + Treatment t = new Treatment(); + t.isSMB = detailedBolusInfo.isSMB; + final EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); + bolusingEvent.t = t; + bolusingEvent.status = String.format(MainApp.sResources.getString(R.string.bolusdelivering), 0F); + bolusingEvent.bolusId = bolusId; + bolusingEvent.percent = 0; + MainApp.bus().post(bolusingEvent); + TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo); + } else { + log.debug("Failure to deliver treatment"); + } + + if (Config.logPumpComm) + log.debug("Delivering treatment insulin: " + detailedBolusInfo.insulin + "U carbs: " + detailedBolusInfo.carbs + "g " + result); + + updateGui(); + connector.tryToGetPumpStatusAgain(); + + connector.requestHistorySync(30000); + + if (result.success) while (true) { + try { + Thread.sleep(200); + } catch (InterruptedException e) { + e.printStackTrace(); + break; + } + final UUID uuid = aSyncSingleCommand(new ActiveBolusesMessage(), "Active boluses"); + Mstatus mstatus = async.busyWaitForCommandResult(uuid, BUSY_WAIT_TIME); + if (mstatus.success()) { + final EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); + ActiveBolusesMessage activeBolusesMessage = (ActiveBolusesMessage) mstatus.getResponseObject(); + ActiveBolus activeBolus = null; + if (activeBolusesMessage.getBolus1() != null && activeBolusesMessage.getBolus1().getBolusID() == bolusingEvent.bolusId) + activeBolus = activeBolusesMessage.getBolus1(); + else if (activeBolusesMessage.getBolus2() != null && activeBolusesMessage.getBolus2().getBolusID() == bolusingEvent.bolusId) + activeBolus = activeBolusesMessage.getBolus2(); + else if (activeBolusesMessage.getBolus3() != null && activeBolusesMessage.getBolus3().getBolusID() == bolusingEvent.bolusId) + activeBolus = activeBolusesMessage.getBolus3(); + if (activeBolus == null) break; + else { + bolusingEvent.percent = (int) (100D / activeBolus.getInitialAmount() * (activeBolus.getInitialAmount() - activeBolus.getLeftoverAmount())); + bolusingEvent.status = String.format(MainApp.sResources.getString(R.string.bolusdelivering), activeBolus.getInitialAmount() - activeBolus.getLeftoverAmount()); + MainApp.bus().post(bolusingEvent); + } + } else break; + } + return result; + } + + @Override + public void stopBolusDelivering() { + CancelBolusMessage cancelBolusMessage = new CancelBolusMessage(); + cancelBolusMessage.setBolusId(EventOverviewBolusProgress.getInstance().bolusId); + final UUID cmd = aSyncSingleCommand(cancelBolusMessage, "Cancel standard bolus"); + + if (cmd == null) { + return; + } + + final Mstatus cs = async.busyWaitForCommandResult(cmd, BUSY_WAIT_TIME); + log("Got command status: " + cs); + } + + // Temporary Basals + + @Override + public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, Profile profile, boolean enforceNew) { + absoluteRate = Helpers.roundDouble(absoluteRate, 3); + log("Set TBR absolute: " + absoluteRate); + final double base_basal = getBaseBasalRate(); + if (base_basal == 0) { + log("Base basal rate appears to be zero!"); + return pumpEnactFailure(); + } + int percent_amount = (int) Math.round(100d / base_basal * absoluteRate); + log("Calculated requested rate: " + absoluteRate + " base rate: " + base_basal + " percentage: " + percent_amount + "%"); + percent_amount = (int) Math.round(((double) percent_amount) / 10d) * 10; + log("Calculated final rate: " + percent_amount + "%"); + + if (percent_amount == 100) { + return cancelTempBasal(false); + } + + if (percent_amount > 250) percent_amount = 250; + + + final SetTBRTaskRunner task = new SetTBRTaskRunner(connector.getServiceConnector(), percent_amount, durationInMinutes); + final UUID cmd = aSyncTaskRunner(task, "Set TBR abs: " + absoluteRate + " " + durationInMinutes + "m"); + + if (cmd == null) { + return pumpEnactFailure(); + } + + Mstatus ms = async.busyWaitForCommandResult(cmd, BUSY_WAIT_TIME); + log("Got command status: " + ms); + + PumpEnactResult pumpEnactResult = new PumpEnactResult().enacted(true).isPercent(true).duration(durationInMinutes); + pumpEnactResult.percent = percent_amount; + pumpEnactResult.success = ms.success(); + pumpEnactResult.comment = ms.getCommandComment(); + + + if (pumpEnactResult.success) { + // create log entry + final TemporaryBasal tempBasal = new TemporaryBasal() + .date(System.currentTimeMillis()) + .percent(percent_amount) + .duration(durationInMinutes) + .source(Source.USER); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempBasal); + } + + if (Config.logPumpComm) + log.debug("Setting temp basal absolute: " + pumpEnactResult.success); + + updateGui(); + + connector.requestHistorySync(5000); + connector.tryToGetPumpStatusAgain(); + + return pumpEnactResult; + } + + + @Override + public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, Profile profile, boolean enforceNew) { + log("Set TBR %"); + + percent = (int) Math.round(((double) percent) / 10d) * 10; + if (percent == 100) { + // This would cause a cancel if a tbr is in progress so treat as a cancel + return cancelTempBasal(false); + } + + + final UUID cmd = aSyncTaskRunner(new SetTBRTaskRunner(connector.getServiceConnector(), percent, durationInMinutes), "Set TBR " + percent + "%" + " " + durationInMinutes + "m"); + + if (cmd == null) { + return pumpEnactFailure(); + } + + final Mstatus ms = async.busyWaitForCommandResult(cmd, BUSY_WAIT_TIME); + log("Got command status: " + ms); + + PumpEnactResult pumpEnactResult = new PumpEnactResult().enacted(true).isPercent(true).duration(durationInMinutes); + pumpEnactResult.percent = percent; + pumpEnactResult.success = ms.success(); + pumpEnactResult.comment = ms.getCommandComment(); + + if (pumpEnactResult.success) { + // create log entry + final TemporaryBasal tempBasal = new TemporaryBasal() + .date(System.currentTimeMillis()) + .percent(percent) + .duration(durationInMinutes) + .source(Source.USER); // TODO check this is correct + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempBasal); + } + + updateGui(); + + if (Config.logPumpComm) + log.debug("Set temp basal " + percent + "% for " + durationInMinutes + "m"); + + connector.requestHistorySync(5000); + connector.tryToGetPumpStatusAgain(); + + return pumpEnactResult; + } + + + @Override + public PumpEnactResult cancelTempBasal(boolean enforceNew) { + log("Cancel TBR"); + + + fauxTBRcancel = !SP.getBoolean("insight_real_tbr_cancel", false); + + final UUID cmd; + + if (fauxTBRcancel) { + cmd = aSyncTaskRunner(new SetTBRTaskRunner(connector.getServiceConnector(), 100, 1), "Faux Cancel TBR - setting " + "90%" + " 1m"); + } else { + cmd = aSyncSingleCommand(new CancelTBRMessage(), "Cancel Temp Basal"); + } + if (cmd == null) { + return pumpEnactFailure(); + } + + // TODO isn't conditional on one apparently being in progress only the history change + final Mstatus ms = async.busyWaitForCommandResult(cmd, BUSY_WAIT_TIME); + + if (TreatmentsPlugin.getPlugin().isTempBasalInProgress()) { + TemporaryBasal tempStop = new TemporaryBasal().date(System.currentTimeMillis()).source(Source.USER); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempStop); + } + updateGui(); + if (Config.logPumpComm) + log.debug("Canceling temp basal: "); // TODO get more info + + connector.requestHistorySync(5000); + connector.tryToGetPumpStatusAgain(); + + return new PumpEnactResult().success(ms.success()).enacted(true).isTempCancel(true); + } + + + // Extended Boluses + + @Override + public PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes) { + log("Set Extended bolus " + insulin + " " + durationInMinutes); + ExtendedBolusMessage extendedBolusMessage = new ExtendedBolusMessage(); + extendedBolusMessage.setAmount(insulin); + extendedBolusMessage.setDuration(durationInMinutes); + final UUID cmd = aSyncSingleCommand(extendedBolusMessage, "Extended bolus U" + insulin + " mins:" + durationInMinutes); + if (cmd == null) { + return pumpEnactFailure(); + } + + final Mstatus ms = async.busyWaitForCommandResult(cmd, BUSY_WAIT_TIME); + log("Got command status: " + ms); + + PumpEnactResult pumpEnactResult = new PumpEnactResult().enacted(true).bolusDelivered(insulin).duration(durationInMinutes); + pumpEnactResult.success = ms.success(); + pumpEnactResult.comment = ms.getCommandComment(); + + if (pumpEnactResult.success) { + // create log entry + final ExtendedBolus extendedBolus = new ExtendedBolus(); + extendedBolus.date = System.currentTimeMillis(); + extendedBolus.insulin = insulin; + extendedBolus.durationInMinutes = durationInMinutes; + extendedBolus.source = Source.USER; + extendedBolus.pumpId = getRecordUniqueID(ms.getResponseID()); + TreatmentsPlugin.getPlugin().addToHistoryExtendedBolus(extendedBolus); + } + + if (Config.logPumpComm) + log.debug("Setting extended bolus: " + insulin + " mins:" + durationInMinutes + " " + pumpEnactResult.comment); + + updateGui(); + + connector.requestHistorySync(30000); + connector.tryToGetPumpStatusAgain(); + + return pumpEnactResult; + } + + @Override + public PumpEnactResult cancelExtendedBolus() { + + log("Cancel Extended bolus"); + + // TODO note always sends cancel to pump but only changes history if present + + final UUID cmd = aSyncTaskRunner(new CancelBolusTaskRunner(connector.getServiceConnector(), ActiveBolusType.EXTENDED), "Cancel extended bolus"); + + if (cmd == null) { + return pumpEnactFailure(); + } + + final Mstatus ms = async.busyWaitForCommandResult(cmd, BUSY_WAIT_TIME); + + if (TreatmentsPlugin.getPlugin().isInHistoryExtendedBoluslInProgress()) { + ExtendedBolus exStop = new ExtendedBolus(System.currentTimeMillis()); + exStop.source = Source.USER; + TreatmentsPlugin.getPlugin().addToHistoryExtendedBolus(exStop); + } + + if (Config.logPumpComm) + log.debug("Cancel extended bolus:"); + + updateGui(); + + connector.requestHistorySync(5000); + connector.tryToGetPumpStatusAgain(); + + return new PumpEnactResult().success(ms.success()).enacted(true); + } + + + private synchronized UUID deliverBolus(double bolusValue) { + log("DeliverBolus: " + bolusValue); + + if (bolusValue == 0) return null; + if (bolusValue < 0) return null; + + // TODO check limits here or they already occur via a previous constraint interface? + + final StandardBolusMessage message = new StandardBolusMessage(); + message.setAmount(bolusValue); + + return aSyncSingleCommand(message, "Deliver Bolus " + bolusValue); + } + + @Override + public JSONObject getJSONStatus(Profile profile, String profileName) { + long now = System.currentTimeMillis(); + if (Helpers.msSince(connector.getLastContactTime()) > (60 * 60 * 1000)) { + log("getJSONStatus not returning as data likely stale"); + return null; + } + + final JSONObject pump = new JSONObject(); + final JSONObject battery = new JSONObject(); + final JSONObject status = new JSONObject(); + final JSONObject extended = new JSONObject(); + try { + battery.put("percent", batteryPercent); + status.put("status", isSuspended() ? "suspended" : "normal"); + status.put("timestamp", DateUtil.toISOString(connector.getLastContactTime())); + extended.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION); + try { + extended.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName()); + } catch (Exception e) { + } + TemporaryBasal tb = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(now); + if (tb != null) { + extended.put("TempBasalAbsoluteRate", tb.tempBasalConvertedToAbsolute(now, profile)); + extended.put("TempBasalStart", DateUtil.dateAndTimeString(tb.date)); + extended.put("TempBasalRemaining", tb.getPlannedRemainingMinutes()); + } + ExtendedBolus eb = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(now); + if (eb != null) { + extended.put("ExtendedBolusAbsoluteRate", eb.absoluteRate()); + extended.put("ExtendedBolusStart", DateUtil.dateAndTimeString(eb.date)); + extended.put("ExtendedBolusRemaining", eb.getPlannedRemainingMinutes()); + } + extended.put("BaseBasalRate", getBaseBasalRate()); + status.put("timestamp", DateUtil.toISOString(now)); + + pump.put("battery", battery); + pump.put("status", status); + pump.put("extended", extended); + pump.put("reservoir", reservoirInUnits); + pump.put("clock", DateUtil.toISOString(now)); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + return pump; + } + + @Override + public String deviceID() { + return "InsightPump"; + } + + @Override + public PumpDescription getPumpDescription() { + return pumpDescription; + } + + @Override + public String shortStatus(boolean veryShort) { + String msg = gs(R.string.insightpump_shortname) + " Batt: " + batteryPercent + " Reserv: " + reservoirInUnits + " Basal: " + basalRate; + if (LiveHistory.getStatus().length() > 0) { + msg += LiveHistory.getStatus(); + } + return msg; + } + + private void processStatusResult() { + if (statusResult != null) { + batteryPercent = statusResult.battery; + reservoirInUnits = (int) statusResult.cartridge; + basalRate = statusResult.baseBasalRate; + profileBlocks = statusResult.basalProfile; + initialized = true; // basic communication test + } + } + + private String gs(int id) { + return MainApp.instance().getString(id); + } + + private boolean isPumpRunning() { + if (statusResult == null) return true; // assume running if we have no information + return statusResult.pumpStatus == PumpStatus.STARTED; + } + + List getStatusItems(boolean refresh) { + final List l = new ArrayList<>(); + + // Todo last contact time + + l.add(new StatusItem(gs(R.string.status_no_colon), connector.getLastStatusMessage())); + l.add(new StatusItem(gs(R.string.changed), connector.getNiceLastStatusTime())); + + boolean pumpRunning; + // also check time since received + if (statusResult != null) { + + pumpRunning = isPumpRunning(); + if (pumpRunning) { + l.add(new StatusItem(gs(R.string.pump_basebasalrate_label), getBaseBasalRateString() + "U")); + } else { + l.add(new StatusItem(gs(R.string.combo_warning), gs(R.string.pump_stopped_uppercase), StatusItem.Highlight.CRITICAL)); + } + } + + final long offset_ms = Helpers.msSince(statusResultTime); + final long offset_minutes = offset_ms / 60000; + + if (statusResult != null) { + l.add(new StatusItem(gs(R.string.status_updated), Helpers.niceTimeScalar(Helpers.msSince(statusResultTime)) + " " + gs(R.string.ago))); + l.add(new StatusItem(gs(R.string.pump_battery_label), batteryPercent + "%", batteryPercent < 100 ? + (batteryPercent < 90 ? + (batteryPercent < 70 ? + (StatusItem.Highlight.BAD) : StatusItem.Highlight.NOTICE) : StatusItem.Highlight.NORMAL) : StatusItem.Highlight.GOOD)); + l.add(new StatusItem(gs(R.string.pump_reservoir_label), reservoirInUnits + "U")); + try { + if (statusResult.tbrAmount != 100) { + l.add(new StatusItem(gs(R.string.insight_active_tbr), statusResult.tbrAmount + "% " + gs(R.string.with) + " " + + Helpers.qs(statusResult.tbrLeftoverDuration - offset_minutes, 0) + + " " + gs(R.string.insight_min_left), StatusItem.Highlight.NOTICE)); + } + } catch (NullPointerException e) { + // currentTBRMessage may be null + } + + } + + if (TreatmentsPlugin.getPlugin().isTempBasalInProgress()) { + try { + l.add(new StatusItem(gs(R.string.pump_tempbasal_label), TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()).toStringFull())); + } catch (NullPointerException e) { + // + } + } + + if (statusResult != null) { + try { + statusActiveBolus(statusResult.activeBolus1, offset_minutes, l); + statusActiveBolus(statusResult.activeBolus2, offset_minutes, l); + statusActiveBolus(statusResult.activeBolus3, offset_minutes, l); + } catch (NullPointerException e) { + // getActiveBolusesMessage() may be null + } + } + + if (TreatmentsPlugin.getPlugin().isInHistoryExtendedBoluslInProgress()) { + try { + + l.add(new StatusItem(gs(R.string.virtualpump_extendedbolus_label), TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(System.currentTimeMillis()).toString())); + } catch (NullPointerException e) { + // + } + } + + l.add(new StatusItem(gs(R.string.log_book), HistoryReceiver.getStatusString())); + + if (LiveHistory.getStatus().length() > 0) { + l.add(new StatusItem(gs(R.string.insight_last_completed_action), LiveHistory.getStatus())); + } + + final String keep_alive_status = Connector.getKeepAliveString(); + if (keep_alive_status != null) { + l.add(new StatusItem(gs(R.string.insight_keep_alive_status), keep_alive_status)); + } + + final List status_statistics = connector.getStatusStatistics(); + if (status_statistics.size() > 0) { + l.addAll(status_statistics); + } + + if (Helpers.ratelimit("insight-status-ui-refresh", 10)) { + connector.tryToGetPumpStatusAgain(); + } + connector.requestHistorySync(); + if (refresh) scheduleGUIUpdate(); + + return l; + } + + private synchronized void scheduleGUIUpdate() { + if (!update_pending && connector.uiFresh()) { + update_pending = true; + Helpers.runOnUiThreadDelayed(new Runnable() { + @Override + public void run() { + updateGui(); + } + }, 1000); + } + } + + private void statusActiveBolus(ActiveBolus activeBolus, long offset_mins, List l) { + if (activeBolus == null) return; + switch (activeBolus.getBolusType()) { + + case STANDARD: + l.add(new StatusItem(activeBolus.getBolusType() + " " + gs(R.string.bolus), activeBolus.getInitialAmount() + "U", StatusItem.Highlight.NOTICE)); + break; + case EXTENDED: + l.add(new StatusItem(activeBolus.getBolusType() + " " + gs(R.string.bolus), activeBolus.getInitialAmount() + "U " + gs(R.string.insight_total_with) + " " + + activeBolus.getLeftoverAmount() + "U " + gs(R.string.insight_remaining_over) + " " + (activeBolus.getDuration() - offset_mins) + " " + gs(R.string.insight_min), StatusItem.Highlight.NOTICE)); + break; + case MULTIWAVE: + l.add(new StatusItem(activeBolus.getBolusType() + " " + gs(R.string.bolus), activeBolus.getInitialAmount() + "U " + gs(R.string.insight_upfront_with) + " " + + activeBolus.getLeftoverAmount() + "U " + gs(R.string.insight_remaining_over) + " " + (activeBolus.getDuration() - offset_mins) + " " + gs(R.string.insight_min), StatusItem.Highlight.NOTICE)); + + break; + default: + log("ERROR: unknown bolus type! " + activeBolus.getBolusType()); + } + } + + // Utility + + private synchronized UUID aSyncSingleCommand(final AppLayerMessage msg, final String name) { + // if (!isConnected()) return false; + //if (isBusy()) return false; + log("asyncSinglecommand called: " + name); + final EventInsightCallback event = new EventInsightCallback(); + new Thread() { + @Override + public void run() { + log("asyncSingleCommand thread"); + final SingleMessageTaskRunner singleMessageTaskRunner = new SingleMessageTaskRunner(connector.getServiceConnector(), msg); + try { + singleMessageTaskRunner.fetch(new TaskRunner.ResultCallback() { + @Override + public void onResult(Object o) { + lastDataTime = new Date(); + log(name + " success"); + event.response_object = o; + if (o instanceof BolusMessage) { + event.response_id = ((BolusMessage) o).getBolusId(); + } + event.success = true; + pushCallbackEvent(event); + } + + @Override + public void onError(Exception e) { + log(name + " error"); + event.message = e.getMessage(); + pushCallbackEvent(event); + } + }); + + } catch (Exception e) { + log("EXCEPTION" + e.toString()); + } + } + }.start(); + return event.request_uuid; + } + + private synchronized UUID aSyncTaskRunner(final TaskRunner task, final String name) { + // if (!isConnected()) return false; + //if (isBusy()) return false; + log("asyncTaskRunner called: " + name); + final EventInsightCallback event = new EventInsightCallback(); + new Thread() { + @Override + public void run() { + log("asyncTaskRunner thread"); + try { + task.fetch(new TaskRunner.ResultCallback() { + @Override + public void onResult(Object o) { + lastDataTime = new Date(); + log(name + " success"); + event.response_object = o; + event.success = true; + pushCallbackEvent(event); + } + + @Override + public void onError(Exception e) { + log(name + " error"); + event.message = e.getMessage(); + pushCallbackEvent(event); + } + }); + + } catch (Exception e) { + log("EXCEPTION" + e.toString()); + } + } + }.start(); + return event.request_uuid; + } + + + private PumpEnactResult pumpEnactFailure() { + return new PumpEnactResult().success(false).enacted(false); + } + + // Constraints + + @Override + public Constraint applyBasalConstraints(Constraint absoluteRate, Profile profile) { + if (statusResult != null) { + absoluteRate.setIfSmaller(statusResult.maximumBasalAmount, String.format(MainApp.gs(R.string.limitingbasalratio), statusResult.maximumBasalAmount, MainApp.gs(R.string.pumplimit)), this); + } + return absoluteRate; + } + + @Override + public Constraint applyBasalPercentConstraints(Constraint percentRate, Profile profile) { + percentRate.setIfGreater(0, String.format(MainApp.gs(R.string.limitingpercentrate), 0, MainApp.gs(R.string.itmustbepositivevalue)), this); + percentRate.setIfSmaller(getPumpDescription().maxTempPercent, String.format(MainApp.gs(R.string.limitingpercentrate), getPumpDescription().maxTempPercent, MainApp.gs(R.string.pumplimit)), this); + + return percentRate; + } + + @Override + public Constraint applyBolusConstraints(Constraint insulin) { + if (statusResult != null) + insulin.setIfSmaller(statusResult.maximumBolusAmount, String.format(MainApp.gs(R.string.limitingbolus), statusResult.maximumBolusAmount, MainApp.gs(R.string.pumplimit)), this); + return insulin; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/Mstatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/Mstatus.java new file mode 100644 index 0000000000..8797325f7c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/Mstatus.java @@ -0,0 +1,50 @@ +package info.nightscout.androidaps.plugins.PumpInsight; + +import info.nightscout.androidaps.plugins.PumpInsight.events.EventInsightCallback; + +/** + * Created by jamorham on 01/02/2018. + * + * Encapsulates results from commands + */ + +class Mstatus { + + Cstatus cstatus = Cstatus.UNKNOWN; + EventInsightCallback event; + + // comment field preparation for results + String getCommandComment() { + if (success()) { + return "OK"; + } else { + return (event == null) ? "EVENT DATA IS NULL - ERROR OR FIREWALL ENABLED?" : event.message; + } + } + + boolean success() { + return cstatus.success(); + } + + int getResponseID() { + if (success()) { + return event.response_id; + } else { + return -2; // invalid + } + } + + Object getResponseObject() { + if (success()) { + return event.response_object; + } else { + return null; + } + } + + @Override + public String toString() { + return cstatus + " " + event; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/connector/CancelBolusTaskRunner.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/connector/CancelBolusTaskRunner.java new file mode 100644 index 0000000000..f350b80851 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/connector/CancelBolusTaskRunner.java @@ -0,0 +1,38 @@ +package info.nightscout.androidaps.plugins.PumpInsight.connector; + +import sugar.free.sightparser.applayer.messages.AppLayerMessage; +import sugar.free.sightparser.applayer.descriptors.ActiveBolusType; +import sugar.free.sightparser.applayer.messages.remote_control.CancelBolusMessage; +import sugar.free.sightparser.applayer.messages.status.ActiveBolusesMessage; +import sugar.free.sightparser.handling.SightServiceConnector; +import sugar.free.sightparser.handling.TaskRunner; + +// by Tebbe Ubben + +public class CancelBolusTaskRunner extends TaskRunner { + + private ActiveBolusType bolusType; + + public CancelBolusTaskRunner(SightServiceConnector serviceConnector, ActiveBolusType bolusType) { + super(serviceConnector); + this.bolusType = bolusType; + } + + @Override + protected AppLayerMessage run(AppLayerMessage message) throws Exception { + if (message == null) return new ActiveBolusesMessage(); + else if (message instanceof ActiveBolusesMessage) { + ActiveBolusesMessage bolusesMessage = (ActiveBolusesMessage) message; + CancelBolusMessage cancelBolusMessage = new CancelBolusMessage(); + if (bolusesMessage.getBolus1().getBolusType() == bolusType) + cancelBolusMessage.setBolusId(bolusesMessage.getBolus1().getBolusID()); + else if (bolusesMessage.getBolus2().getBolusType() == bolusType) + cancelBolusMessage.setBolusId(bolusesMessage.getBolus2().getBolusID()); + else if (bolusesMessage.getBolus3().getBolusType() == bolusType) + cancelBolusMessage.setBolusId(bolusesMessage.getBolus3().getBolusID()); + else finish(null); + return cancelBolusMessage; + } else if (message instanceof CancelBolusMessage) finish(null); + return null; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/connector/Connector.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/connector/Connector.java new file mode 100644 index 0000000000..a96c98a39a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/connector/Connector.java @@ -0,0 +1,554 @@ +package info.nightscout.androidaps.plugins.PumpInsight.connector; + +import android.content.Intent; +import android.os.PowerManager; + +import com.squareup.otto.Subscribe; + +import java.util.ArrayList; +import java.util.Formatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.events.EventFeatureRunning; +import info.nightscout.androidaps.plugins.PumpInsight.events.EventInsightUpdateGui; +import info.nightscout.androidaps.plugins.PumpInsight.history.HistoryReceiver; +import info.nightscout.androidaps.plugins.PumpInsight.history.LiveHistory; +import info.nightscout.androidaps.plugins.PumpInsight.utils.Helpers; +import info.nightscout.androidaps.plugins.PumpInsight.utils.StatusItem; +import info.nightscout.utils.SP; +import sugar.free.sightparser.handling.ServiceConnectionCallback; +import sugar.free.sightparser.handling.SightServiceConnector; +import sugar.free.sightparser.handling.StatusCallback; +import sugar.free.sightparser.pipeline.Status; + +import static sugar.free.sightparser.handling.HistoryBroadcast.ACTION_START_RESYNC; +import static sugar.free.sightparser.handling.HistoryBroadcast.ACTION_START_SYNC; +import static sugar.free.sightparser.handling.SightService.COMPATIBILITY_VERSION; + +/** + * Created by jamorham on 23/01/2018. + * + * Connects to SightRemote app service using SightParser library + * + * SightRemote and SightParser created by Tebbe Ubben + * + * Original proof of concept SightProxy by jamorham + * + */ + +public class Connector { + + // TODO connection statistics + + private static final String TAG = "InsightConnector"; + private static final String COMPANION_APP_PACKAGE = "sugar.free.sightremote"; + private final static long FRESH_MS = 70000; + private static final Map statistics = new HashMap<>(); + private static volatile Connector instance; + private static volatile HistoryReceiver historyReceiver; + private static volatile long stayConnectedTill = -1; + private static volatile long stayConnectedTime = 0; + private static volatile boolean disconnect_thread_running = false; + private volatile SightServiceConnector serviceConnector; + private volatile Status lastStatus = null; + private String compatabilityMessage = null; + private volatile long lastStatusTime = -1; + private volatile long lastContactTime = -1; + private boolean companionAppInstalled = false; + private int serviceReconnects = 0; + private StatusCallback statusCallback = new StatusCallback() { + @Override + public synchronized void onStatusChange(Status status) { + + if ((status != lastStatus) || (Helpers.msSince(lastStatusTime) > 2000)) { + log("Status change: " + status); + + updateStatusStatistics(lastStatus, lastStatusTime); + lastStatus = status; + lastStatusTime = Helpers.tsl(); + + if (status == Status.CONNECTED) { + lastContactTime = lastStatusTime; + extendKeepAliveIfActive(); + } + + MainApp.bus().post(new EventInsightUpdateGui()); + } else { + log("Same status as before: " + status); + } + } + + }; + private ServiceConnectionCallback connectionCallback = new ServiceConnectionCallback() { + + @Override + public synchronized void onServiceConnected() { + log("On service connected"); + try { + final String remoteVersion = serviceConnector.getRemoteVersion(); + if (remoteVersion.equals(COMPATIBILITY_VERSION)) { + serviceConnector.connect(); + } else { + log("PROTOCOL VERSION MISMATCH! local: " + COMPATIBILITY_VERSION + " remote: " + remoteVersion); + statusCallback.onStatusChange(Status.INCOMPATIBLE); + compatabilityMessage = gs(R.string.insight_incompatible_compantion_app_we_need_version) + " " + getLocalVersion(); + serviceConnector.disconnectFromService(); + + } + } catch (NullPointerException e) { + log("ERROR: null pointer when trying to connect to pump"); + } + statusCallback.onStatusChange(safeGetStatus()); + } + + @Override + public synchronized void onServiceDisconnected() { + log("Disconnected from service"); + if (Helpers.ratelimit("insight-automatic-reconnect", 30)) { + log("Scheduling automatic service reconnection"); + Helpers.runOnUiThreadDelayed(new Runnable() { + @Override + public void run() { + init(); + } + }, 20000); + } + } + }; + + private Connector() { + initializeHistoryReceiver(); + MainApp.bus().register(this); + } + + public static Connector get() { + if (instance == null) { + init_instance(); + } + return instance; + } + + private synchronized static void init_instance() { + if (instance == null) { + instance = new Connector(); + } + } + + private static boolean isCompanionAppInstalled() { + return Helpers.checkPackageExists(MainApp.instance(), TAG, COMPANION_APP_PACKAGE); + } + + public static void connectToPump() { + connectToPump(0); + } + + public synchronized static void connectToPump(long keep_alive) { + log("Attempting to connect to pump."); + if (keep_alive > 0 && Helpers.tsl() + keep_alive > stayConnectedTill) { + stayConnectedTime = keep_alive; + stayConnectedTill = Helpers.tsl() + keep_alive; + log("Staying connected till: " + Helpers.dateTimeText(stayConnectedTill)); + delayedDisconnectionThread(); + } + get().getServiceConnector().connect(); + } + + public static void disconnectFromPump() { + if (Helpers.tsl() >= stayConnectedTill) { + log("Requesting real pump disconnect"); + get().getServiceConnector().disconnect(); + } else { + log("Cannot disconnect as due to keep alive till: " + Helpers.dateTimeText(stayConnectedTill)); + // TODO set a disconnection timer? + } + } + + static void log(String msg) { + android.util.Log.e("INSIGHTPUMP", msg); + } + + static String getLocalVersion() { + return COMPATIBILITY_VERSION; + } + + private static String statusToString(Status status) { + switch (status) { + + case EXCHANGING_KEYS: + return gs(R.string.connecting).toUpperCase(); + case WAITING_FOR_CODE_CONFIRMATION: + return gs(R.string.insight_waiting_for_code).toUpperCase(); + case CODE_REJECTED: + return gs(R.string.insight_code_rejected).toUpperCase(); + case APP_BINDING: + return gs(R.string.insight_app_binding).toUpperCase(); + case CONNECTING: + return gs(R.string.connecting).toUpperCase(); + case CONNECTED: + return gs(R.string.connected).toUpperCase(); + case DISCONNECTED: + return gs(R.string.disconnected).toUpperCase(); + case NOT_AUTHORIZED: + return gs(R.string.insight_not_authorized).toUpperCase(); + case INCOMPATIBLE: + return gs(R.string.insight_incompatible).toUpperCase(); + + default: + return status.toString(); + } + } + + private static String gs(int id) { + return MainApp.instance().getString(id); + } + + private static synchronized void extendKeepAliveIfActive() { + if (keepAliveActive()) { + if (Helpers.ratelimit("extend-insight-keepalive", 10)) { + stayConnectedTill = Helpers.tsl() + stayConnectedTime; + log("Keep-alive extended until: " + Helpers.dateTimeText(stayConnectedTill)); + } + } + } + + private static boolean keepAliveActive() { + return Helpers.tsl() <= stayConnectedTill; + } + + public static String getKeepAliveString() { + if (keepAliveActive()) { + return MainApp.instance().getString(R.string.insight_keepalive_format_string, + stayConnectedTime / 1000, Helpers.hourMinuteSecondString(stayConnectedTill)); + + } else { + return null; + } + } + + private static synchronized void delayedDisconnectionThread() { + if (keepAliveActive()) { + if (!disconnect_thread_running) { + disconnect_thread_running = true; + new Thread(new Runnable() { + @Override + public void run() { + final PowerManager.WakeLock wl = Helpers.getWakeLock("insight-disconnection-timer", 600000); + try { + while (disconnect_thread_running && keepAliveActive()) { + if (Helpers.ratelimit("insight-expiry-notice", 5)) { + log("Staying connected thread expires: " + Helpers.dateTimeText(stayConnectedTill)); + } + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // + } + } + + if (disconnect_thread_running) { + log("Sending the real delayed disconnect"); + get().getServiceConnector().disconnect(); + } else { + log("Disconnect thread already terminating"); + } + } finally { + Helpers.releaseWakeLock(wl); + disconnect_thread_running = false; + } + } + }).start(); + } else { + log("Disconnect thread already running"); + } + } + } + + private static long percentage(long t, long total) { + return (long) (Helpers.roundDouble(((double) t * 100) / total, 0)); + } + + public synchronized void shutdown() { + if (instance != null) { + log("Attempting to shut down connector"); + try { + disconnect_thread_running = false; + try { + instance.serviceConnector.setConnectionCallback(null); + } catch (Exception e) { + // + } + try { + instance.serviceConnector.removeStatusCallback(statusCallback); + } catch (Exception e) { + // + } + try { + instance.serviceConnector.disconnect(); + } catch (Exception e) { + log("Exception disconnecting: " + e); + } + try { + instance.serviceConnector.disconnectFromService(); + } catch (Exception e) { + log("Excpetion disconnecting service: " + e); + } + instance.serviceConnector = null; + instance = null; + } catch (Exception e) { + log("Exception shutting down: " + e); + } + } + } + + @SuppressWarnings("AccessStaticViaInstance") + private synchronized void initializeHistoryReceiver() { + if (historyReceiver == null) { + historyReceiver = new HistoryReceiver(); + } + historyReceiver.registerHistoryReceiver(); + } + + public synchronized void init() { + log("Connector::init()"); + if (serviceConnector == null) { + companionAppInstalled = isCompanionAppInstalled(); + if (companionAppInstalled) { + serviceConnector = new SightServiceConnector(MainApp.instance()); + serviceConnector.removeStatusCallback(statusCallback); + serviceConnector.addStatusCallback(statusCallback); + serviceConnector.setConnectionCallback(connectionCallback); + serviceConnector.connectToService(); + log("Trying to connect"); + } else { + log("Not trying init due to missing companion app"); + } + } else { + if (!serviceConnector.isConnectedToService()) { + if (serviceReconnects > 0) { + serviceConnector = null; + init(); + } else { + log("Trying to reconnect to service (" + serviceReconnects + ")"); + serviceConnector.connectToService(); + serviceReconnects++; + } + } else { + serviceReconnects = 0; // everything ok + } + } + } + + public SightServiceConnector getServiceConnector() { + init(); + return serviceConnector; + } + + public String getCurrent() { + init(); + return safeGetStatus().toString(); + } + + public Status safeGetStatus() { + try { + if (isConnected()) return serviceConnector.getStatus(); + return Status.DISCONNECTED; + } catch (IllegalArgumentException e) { + return Status.INCOMPATIBLE; + } + } + + public Status getLastStatus() { + return lastStatus; + } + + public boolean isConnected() { + return serviceConnector != null && serviceConnector.isConnectedToService(); + } + + public boolean isPumpConnected() { + return isConnected() && getLastStatus() == Status.CONNECTED; + } + + public boolean isPumpConnecting() { + return isConnected() && getLastStatus() == Status.CONNECTING; + } + + public long getLastContactTime() { + return lastContactTime; + } + + public String getLastStatusMessage() { + + if (!companionAppInstalled) { + return gs(R.string.insight_companion_app_not_installed); + } + + if (!isConnected()) { + log("Not connected to companion"); + if (Helpers.ratelimit("insight-app-not-connected", 5)) { + init(); + } + + if ((lastStatus == null) || (lastStatus != Status.INCOMPATIBLE)) { + if (compatabilityMessage != null) { + // if disconnected but previous state was incompatible + return compatabilityMessage; + } else { + return gs(R.string.insight_not_connected_to_companion_app); + } + } + } + + if (lastStatus == null) { + return gs(R.string.insight_unknown); + } + + switch (lastStatus) { + case CONNECTED: + if (Helpers.msSince(lastStatusTime) > (60 * 10 * 1000)) { + tryToGetPumpStatusAgain(); + } + break; + case INCOMPATIBLE: + return statusToString(lastStatus) + " " + gs(R.string.insight_needs) + " " + getLocalVersion(); + } + return statusToString(lastStatus); + } + + public String getNiceLastStatusTime() { + if (lastStatusTime < 1) { + return gs(R.string.insight_startup_uppercase); + } else { + return Helpers.niceTimeScalar(Helpers.msSince(lastStatusTime)) + " " + gs(R.string.ago); + } + } + + public boolean uiFresh() { + // todo check other changes + + if (Helpers.msSince(lastStatusTime) < FRESH_MS) { + return true; + } + if (Helpers.msSince(LiveHistory.getStatusTime()) < FRESH_MS) { + return true; + } + return false; + } + + @SuppressWarnings("AccessStaticViaInstance") + public void tryToGetPumpStatusAgain() { + if (Helpers.ratelimit("insight-retry-status-request", 5)) { + try { + MainApp.getConfigBuilder().getCommandQueue().readStatus("Insight. Status missing", null); + } catch (NullPointerException e) { + // + } + } + } + + public void requestHistorySync() { + requestHistorySync(0); + } + + public void requestHistoryReSync() { + requestHistoryReSync(0); + } + + public void requestHistorySync(long delay) { + if (Helpers.ratelimit("insight-history-sync-request", 10)) { + final Intent intent = new Intent(ACTION_START_SYNC); + sendBroadcastToCompanion(intent, delay); + } + } + + public void requestHistoryReSync(long delay) { + if (Helpers.ratelimit("insight-history-resync-request", 300)) { + final Intent intent = new Intent(ACTION_START_RESYNC); + sendBroadcastToCompanion(intent, delay); + } + } + + private void sendBroadcastToCompanion(final Intent intent, final long delay) { + new Thread(new Runnable() { + @Override + public void run() { + final PowerManager.WakeLock wl = Helpers.getWakeLock("insight-companion-delay", 60000); + intent.setPackage(COMPANION_APP_PACKAGE); + intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + try { + if (delay > 0) { + + Thread.sleep(delay); + } + } catch (InterruptedException e) { + // + } finally { + Helpers.releaseWakeLock(wl); + } + MainApp.instance().sendBroadcast(intent); + } + }).start(); + } + + public boolean lastStatusRecent() { + return true; // TODO evaluate whether current + } + + private void updateStatusStatistics(Status last, long since) { + if ((last != null) && (since > 0)) { + Long total = statistics.get(last); + if (total == null) total = 0L; + statistics.put(last, total + Helpers.msSince(since)); + log("Updated statistics for: " + last + " total: " + Helpers.niceTimeScalar(statistics.get(last))); + // TODO persist data + } + } + + public List getStatusStatistics() { + final List l = new ArrayList<>(); + long total = 0; + for (Map.Entry entry : statistics.entrySet()) { + total += getEntryTime(entry); + } + for (Map.Entry entry : statistics.entrySet()) { + if ((long) entry.getValue() > 1000) { + l.add(new StatusItem(gs(R.string.statistics) + " " + Helpers.capitalize(entry.getKey().toString()), + new Formatter().format("%4s %12s", + percentage(getEntryTime(entry), total) + "%", + Helpers.niceTimeScalar(getEntryTime(entry))).toString())); + } + } + return l; + } + + private long getEntryTime(Map.Entry entry) { + return (long) entry.getValue() + (entry.getKey().equals(lastStatus) ? Helpers.msSince(lastStatusTime) : 0); + } + + @Subscribe + public void onStatusEvent(final EventFeatureRunning ev) { + new Thread(new Runnable() { + @Override + public void run() { + if (isConnected()) { + if (SP.getBoolean("insight_preemptive_connect", true)) { + switch (ev.getFeature()) { + case WIZARD: + log("Wizard feature detected, preconnecting to pump"); + connectToPump(120 * 1000); + break; + case MAIN: + log("Main feature detected, preconnecting to pump"); + connectToPump(30 * 1000); + break; + } + } + } + } + }).start(); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/connector/SetTBRTaskRunner.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/connector/SetTBRTaskRunner.java new file mode 100644 index 0000000000..1506ecb4a9 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/connector/SetTBRTaskRunner.java @@ -0,0 +1,51 @@ +package info.nightscout.androidaps.plugins.PumpInsight.connector; + +import sugar.free.sightparser.applayer.messages.AppLayerMessage; +import sugar.free.sightparser.applayer.messages.remote_control.ChangeTBRMessage; +import sugar.free.sightparser.applayer.messages.remote_control.SetTBRMessage; +import sugar.free.sightparser.applayer.messages.status.CurrentTBRMessage; +import sugar.free.sightparser.handling.SightServiceConnector; +import sugar.free.sightparser.handling.TaskRunner; + +// from Tebbe - note this uses 1 minute duration to silently cancel existing TBR + +public class SetTBRTaskRunner extends TaskRunner { + + private int amount; + private int duration; + + public SetTBRTaskRunner(SightServiceConnector serviceConnector, int amount, int duration) { + super(serviceConnector); + this.amount = amount; + this.duration = duration; + } + + @Override + protected AppLayerMessage run(AppLayerMessage message) throws Exception { + if (message == null) return new CurrentTBRMessage(); + else if (message instanceof CurrentTBRMessage) { + if (((CurrentTBRMessage) message).getPercentage() == 100) { + if (amount == 100) finish(amount); + else { + SetTBRMessage setTBRMessage = new SetTBRMessage(); + setTBRMessage.setDuration(duration); + setTBRMessage.setAmount(amount); + return setTBRMessage; + } + } else { + if (amount == 100) { + ChangeTBRMessage changeTBRMessage = new ChangeTBRMessage(); + changeTBRMessage.setDuration(1); + changeTBRMessage.setAmount(90); + return changeTBRMessage; + } else { + ChangeTBRMessage changeTBRMessage = new ChangeTBRMessage(); + changeTBRMessage.setDuration(duration); + changeTBRMessage.setAmount(amount); + return changeTBRMessage; + } + } + } else if (message instanceof SetTBRMessage) finish(amount); + return null; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/connector/StatusTaskRunner.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/connector/StatusTaskRunner.java new file mode 100644 index 0000000000..86e01f98f8 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/connector/StatusTaskRunner.java @@ -0,0 +1,126 @@ +package info.nightscout.androidaps.plugins.PumpInsight.connector; + +import java.util.List; + +import sugar.free.sightparser.applayer.descriptors.ActiveBolus; +import sugar.free.sightparser.applayer.descriptors.PumpStatus; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.ActiveProfileBlock; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.BRProfile1Block; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.BRProfile2Block; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.BRProfile3Block; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.BRProfile4Block; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.BRProfile5Block; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.BRProfileBlock; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.ConfigurationBlock; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.MaxBRAmountBlock; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.MaxBolusAmountBlock; +import sugar.free.sightparser.applayer.messages.AppLayerMessage; +import sugar.free.sightparser.applayer.messages.configuration.ReadConfigurationBlockMessage; +import sugar.free.sightparser.applayer.messages.status.ActiveBolusesMessage; +import sugar.free.sightparser.applayer.messages.status.BatteryAmountMessage; +import sugar.free.sightparser.applayer.messages.status.CartridgeAmountMessage; +import sugar.free.sightparser.applayer.messages.status.CurrentBasalMessage; +import sugar.free.sightparser.applayer.messages.status.CurrentTBRMessage; +import sugar.free.sightparser.applayer.messages.status.PumpStatusMessage; +import sugar.free.sightparser.handling.SightServiceConnector; +import sugar.free.sightparser.handling.TaskRunner; + +/** + * Created by Tebbe Ubben on 12.03.2018. + */ + +public class StatusTaskRunner extends TaskRunner { + + private Result result = new Result(); + + public StatusTaskRunner(SightServiceConnector serviceConnector) { + super(serviceConnector); + } + + @Override + protected AppLayerMessage run(AppLayerMessage message) throws Exception { + if (message == null) return new PumpStatusMessage(); + else if (message instanceof PumpStatusMessage) { + result.pumpStatus = ((PumpStatusMessage) message).getPumpStatus(); + if (result.pumpStatus == PumpStatus.STOPPED) return new BatteryAmountMessage(); + else return new CurrentTBRMessage(); + } else if (message instanceof CurrentTBRMessage) { + CurrentTBRMessage currentTBRMessage = (CurrentTBRMessage) message; + result.tbrAmount = currentTBRMessage.getPercentage(); + result.tbrInitialDuration = currentTBRMessage.getInitialTime(); + result.tbrLeftoverDuration = currentTBRMessage.getLeftoverTime(); + return new ActiveBolusesMessage(); + } else if (message instanceof ActiveBolusesMessage) { + ActiveBolusesMessage activeBolusesMessage = (ActiveBolusesMessage) message; + result.activeBolus1 = activeBolusesMessage.getBolus1(); + result.activeBolus2 = activeBolusesMessage.getBolus2(); + result.activeBolus3 = activeBolusesMessage.getBolus3(); + return new CurrentBasalMessage(); + } else if (message instanceof CurrentBasalMessage) { + result.baseBasalRate = ((CurrentBasalMessage) message).getCurrentBasalAmount(); + return new BatteryAmountMessage(); + } else if (message instanceof BatteryAmountMessage) { + result.battery = ((BatteryAmountMessage) message).getBatteryAmount(); + return new CartridgeAmountMessage(); + } else if (message instanceof CartridgeAmountMessage) { + result.cartridge = ((CartridgeAmountMessage) message).getCartridgeAmount(); + ReadConfigurationBlockMessage readMessage = new ReadConfigurationBlockMessage(); + readMessage.setConfigurationBlockID(ActiveProfileBlock.ID); + return readMessage; + } else if (message instanceof ReadConfigurationBlockMessage) { + ConfigurationBlock configurationBlock = ((ReadConfigurationBlockMessage) message).getConfigurationBlock(); + if (configurationBlock instanceof ActiveProfileBlock) { + ActiveProfileBlock activeProfileBlock = (ActiveProfileBlock) configurationBlock; + ReadConfigurationBlockMessage readMessage = new ReadConfigurationBlockMessage(); + switch (activeProfileBlock.getActiveProfile()) { + case BR_PROFILE_1: + readMessage.setConfigurationBlockID(BRProfile1Block.ID); + break; + case BR_PROFILE_2: + readMessage.setConfigurationBlockID(BRProfile2Block.ID); + break; + case BR_PROFILE_3: + readMessage.setConfigurationBlockID(BRProfile3Block.ID); + break; + case BR_PROFILE_4: + readMessage.setConfigurationBlockID(BRProfile4Block.ID); + break; + case BR_PROFILE_5: + readMessage.setConfigurationBlockID(BRProfile5Block.ID); + break; + } + return readMessage; + } else if (configurationBlock instanceof BRProfileBlock) { + result.basalProfile = ((BRProfileBlock) configurationBlock).getProfileBlocks(); + ReadConfigurationBlockMessage readMessage = new ReadConfigurationBlockMessage(); + readMessage.setConfigurationBlockID(MaxBolusAmountBlock.ID); + return readMessage; + } else if (configurationBlock instanceof MaxBolusAmountBlock) { + result.maximumBolusAmount = ((MaxBolusAmountBlock) configurationBlock).getMaximumAmount(); + ReadConfigurationBlockMessage readMessage = new ReadConfigurationBlockMessage(); + readMessage.setConfigurationBlockID(MaxBRAmountBlock.ID); + return readMessage; + } else if (configurationBlock instanceof MaxBRAmountBlock) { + result.maximumBasalAmount = ((MaxBRAmountBlock) configurationBlock).getMaximumAmount(); + finish(result); + } + } + return null; + } + + public static class Result { + public PumpStatus pumpStatus; + public double baseBasalRate; + public int battery; + public double cartridge ; + public int tbrAmount = 100; + public int tbrInitialDuration = 0; + public int tbrLeftoverDuration = 0; + public ActiveBolus activeBolus1; + public ActiveBolus activeBolus2; + public ActiveBolus activeBolus3; + public List basalProfile; + public double maximumBolusAmount; + public double maximumBasalAmount; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/connector/WriteBasalProfileTaskRunner.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/connector/WriteBasalProfileTaskRunner.java new file mode 100644 index 0000000000..7012d200e7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/connector/WriteBasalProfileTaskRunner.java @@ -0,0 +1,72 @@ +package info.nightscout.androidaps.plugins.PumpInsight.connector; + +import java.util.List; + +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.ActiveProfileBlock; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.BRProfile1Block; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.BRProfile2Block; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.BRProfile3Block; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.BRProfile4Block; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.BRProfile5Block; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.BRProfileBlock; +import sugar.free.sightparser.applayer.descriptors.configuration_blocks.ConfigurationBlock; +import sugar.free.sightparser.applayer.messages.AppLayerMessage; +import sugar.free.sightparser.applayer.messages.configuration.CloseWriteSessionMessage; +import sugar.free.sightparser.applayer.messages.configuration.OpenWriteSessionMessage; +import sugar.free.sightparser.applayer.messages.configuration.ReadConfigurationBlockMessage; +import sugar.free.sightparser.applayer.messages.configuration.WriteConfigurationBlockMessage; +import sugar.free.sightparser.handling.SightServiceConnector; +import sugar.free.sightparser.handling.TaskRunner; + +/** + * Created by Tebbe Ubben on 10.03.2018. + */ + +public class WriteBasalProfileTaskRunner extends TaskRunner { + + private List profileBlocks; + private BRProfileBlock profileBlock; + + public WriteBasalProfileTaskRunner(SightServiceConnector serviceConnector, List profileBlocks) { + super(serviceConnector); + this.profileBlocks = profileBlocks; + } + + @Override + protected AppLayerMessage run(AppLayerMessage message) throws Exception { + if (message == null) { + ReadConfigurationBlockMessage readMessage = new ReadConfigurationBlockMessage(); + readMessage.setConfigurationBlockID(ActiveProfileBlock.ID); + return readMessage; + } else if (message instanceof ReadConfigurationBlockMessage) { + ConfigurationBlock configurationBlock = ((ReadConfigurationBlockMessage) message).getConfigurationBlock(); + ActiveProfileBlock activeProfileBlock = (ActiveProfileBlock) configurationBlock; + switch (activeProfileBlock.getActiveProfile()) { + case BR_PROFILE_1: + profileBlock = new BRProfile1Block(); + break; + case BR_PROFILE_2: + profileBlock = new BRProfile2Block(); + break; + case BR_PROFILE_3: + profileBlock = new BRProfile3Block(); + break; + case BR_PROFILE_4: + profileBlock = new BRProfile4Block(); + break; + case BR_PROFILE_5: + profileBlock = new BRProfile5Block(); + break; + } + profileBlock.setProfileBlocks(profileBlocks); + return new OpenWriteSessionMessage(); + } else if (message instanceof OpenWriteSessionMessage) { + WriteConfigurationBlockMessage writeMessage = new WriteConfigurationBlockMessage(); + writeMessage.setConfigurationBlock(profileBlock); + return writeMessage; + } else if (message instanceof WriteConfigurationBlockMessage) { + return new CloseWriteSessionMessage(); + } else if (message instanceof CloseWriteSessionMessage) finish(null); + return null; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/events/EventInsightCallback.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/events/EventInsightCallback.java new file mode 100644 index 0000000000..16cb5af457 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/events/EventInsightCallback.java @@ -0,0 +1,27 @@ +package info.nightscout.androidaps.plugins.PumpInsight.events; + +import java.util.UUID; + +import info.nightscout.androidaps.events.Event; + +/** + * Created by jamorham on 23/01/2018. + */ +public class EventInsightCallback extends Event { + + public UUID request_uuid; + public boolean success = false; + public String message = null; + public int response_id = -1; + public Object response_object = null; + + public EventInsightCallback() { + request_uuid = UUID.randomUUID(); + } + + @Override + public String toString() { + return "Event: " + request_uuid + " success: " + success + " msg: " + message + " Object: " + response_object; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/events/EventInsightUpdateGui.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/events/EventInsightUpdateGui.java new file mode 100644 index 0000000000..3741c607c4 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/events/EventInsightUpdateGui.java @@ -0,0 +1,9 @@ +package info.nightscout.androidaps.plugins.PumpInsight.events; + +import info.nightscout.androidaps.events.EventUpdateGui; + +/** + * Created by jamorham on 23/01/2018. + */ +public class EventInsightUpdateGui extends EventUpdateGui { +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/history/HistoryIntentAdapter.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/history/HistoryIntentAdapter.java new file mode 100644 index 0000000000..244537af3e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/history/HistoryIntentAdapter.java @@ -0,0 +1,239 @@ +package info.nightscout.androidaps.plugins.PumpInsight.history; + +import android.content.Intent; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.db.CareportalEvent; +import info.nightscout.androidaps.db.TDD; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.NSUpload; +import info.nightscout.utils.SP; +import org.json.JSONException; +import org.json.JSONObject; +import sugar.free.sightparser.handling.HistoryBroadcast; + +import java.util.Date; + +import static info.nightscout.androidaps.plugins.PumpInsight.history.PumpIdCache.updatePumpSerialNumber; + +/** + * Created by jamorham on 27/01/2018. + *

+ * Parse inbound logbook intents + */ + +class HistoryIntentAdapter { + + private HistoryLogAdapter logAdapter = new HistoryLogAdapter(); + + private static Date getDateExtra(Intent intent, String name) { + return (Date) intent.getSerializableExtra(name); + } + + private static void log(String msg) { + android.util.Log.e("HistoryIntentAdapter", msg); + } + + static long getRecordUniqueID(long pump_serial_number, long pump_record_id) { + updatePumpSerialNumber(pump_serial_number); + return (pump_serial_number * 10000000) + pump_record_id; + } + + void processTBRIntent(Intent intent) { + + final int pump_tbr_duration = intent.getIntExtra(HistoryBroadcast.EXTRA_DURATION, -1); + final int pump_tbr_percent = intent.getIntExtra(HistoryBroadcast.EXTRA_TBR_AMOUNT, -1); + long pump_record_id = intent.getLongExtra(HistoryBroadcast.EXTRA_EVENT_NUMBER, -1); + if (pump_record_id == -1) { + pump_record_id = intent.getIntExtra(HistoryBroadcast.EXTRA_EVENT_NUMBER, -1); + } + final long pump_serial_number = Long.parseLong(intent.getStringExtra(HistoryBroadcast.EXTRA_PUMP_SERIAL_NUMBER)); + final Date event_time = getDateExtra(intent, HistoryBroadcast.EXTRA_EVENT_TIME); + final Date start_time = getDateExtra(intent, HistoryBroadcast.EXTRA_START_TIME); + + if ((pump_tbr_duration == -1) || (pump_tbr_percent == -1) || (pump_record_id == -1)) { + log("Invalid TBR record!!!"); + return; + } + + final long record_unique_id = getRecordUniqueID(pump_serial_number, pump_record_id); + + // other sanity checks + if ((pump_tbr_percent == 90) && (pump_tbr_duration <= 1)) { + log("Not creating TBR record for faux cancel"); + } else { + log("Creating TBR record: " + pump_tbr_percent + "% " + pump_tbr_duration + "m" + " id:" + record_unique_id); + logAdapter.createTBRrecord(start_time, pump_tbr_percent, pump_tbr_duration, record_unique_id); + } + } + + void processDeliveredBolusIntent(Intent intent) { + + final String bolus_type = intent.getStringExtra(HistoryBroadcast.EXTRA_BOLUS_TYPE); + final int bolus_id = intent.getIntExtra(HistoryBroadcast.EXTRA_BOLUS_ID, -1); + long pump_record_id = intent.getLongExtra(HistoryBroadcast.EXTRA_EVENT_NUMBER, -1); + if (pump_record_id == -1) { + pump_record_id = intent.getIntExtra(HistoryBroadcast.EXTRA_EVENT_NUMBER, -1); + } + final long pump_serial_number = Long.parseLong(intent.getStringExtra(HistoryBroadcast.EXTRA_PUMP_SERIAL_NUMBER)); + final Date event_time = getDateExtra(intent, HistoryBroadcast.EXTRA_EVENT_TIME); + final Date start_time = getDateExtra(intent, HistoryBroadcast.EXTRA_START_TIME); + final double immediate_amount = intent.getDoubleExtra(HistoryBroadcast.EXTRA_IMMEDIATE_AMOUNT, -1); + final double extended_insulin = intent.getDoubleExtra(HistoryBroadcast.EXTRA_EXTENDED_AMOUNT, -1); + final int extended_minutes = intent.getIntExtra(HistoryBroadcast.EXTRA_DURATION, -1); + + final long record_unique_id = getRecordUniqueID(pump_serial_number, bolus_id > -1 ? bolus_id : pump_record_id); + + switch (bolus_type) { + case "STANDARD": + if (immediate_amount == -1) { + log("ERROR Standard bolus fails sanity check"); + return; + } + LiveHistory.setStatus(bolus_type + " BOLUS\n" + immediate_amount + "U ", event_time.getTime()); + logAdapter.createStandardBolusRecord(start_time, immediate_amount, record_unique_id); + break; + + case "EXTENDED": + if ((extended_insulin == -1) || (extended_minutes == -1)) { + log("ERROR: Extended bolus fails sanity check"); + return; + } + LiveHistory.setStatus(bolus_type + " BOLUS\n" + extended_insulin + "U over " + extended_minutes + " min, ", event_time.getTime()); + logAdapter.createExtendedBolusRecord(start_time, extended_insulin, extended_minutes, record_unique_id); + break; + + case "MULTIWAVE": + if ((immediate_amount == -1) || (extended_insulin == -1) || (extended_minutes == -1)) { + log("ERROR: Multiwave bolus fails sanity check"); + return; + } + LiveHistory.setStatus(bolus_type + " BOLUS\n" + immediate_amount + "U + " + extended_insulin + "U over " + extended_minutes + " min, ", event_time.getTime()); + logAdapter.createStandardBolusRecord(start_time, immediate_amount, pump_serial_number + pump_record_id); + logAdapter.createExtendedBolusRecord(start_time, extended_insulin, extended_minutes, record_unique_id); + break; + default: + log("ERROR, UNKNWON BOLUS TYPE: " + bolus_type); + } + } + + void processDailyTotalIntent(Intent intent) { + Date date = getDateExtra(intent, HistoryBroadcast.EXTRA_TOTAL_DATE); + double basal = intent.getDoubleExtra(HistoryBroadcast.EXTRA_BASAL_TOTAL, 0D); + double bolus = intent.getDoubleExtra(HistoryBroadcast.EXTRA_BOLUS_TOTAL, 0D); + TDD tdd = new TDD(date.getTime(), bolus, basal, bolus + basal); + MainApp.getDbHelper().createOrUpdateTDD(tdd); + } + + void processCannulaFilledIntent(Intent intent) { + Date date = getDateExtra(intent, HistoryBroadcast.EXTRA_EVENT_TIME); + uploadCareportalEvent(date, CareportalEvent.SITECHANGE); + } + + void processCartridgeInsertedIntent(Intent intent) { + Date date = getDateExtra(intent, HistoryBroadcast.EXTRA_EVENT_TIME); + uploadCareportalEvent(date, CareportalEvent.INSULINCHANGE); + } + + void processBatteryInsertedIntent(Intent intent) { + Date date = getDateExtra(intent, HistoryBroadcast.EXTRA_EVENT_TIME); + uploadCareportalEvent(date, CareportalEvent.PUMPBATTERYCHANGE); + } + + private void uploadCareportalEvent(Date date, String event) { + if (SP.getBoolean("insight_automatic_careportal_events", false)) { + if (MainApp.getDbHelper().getCareportalEventFromTimestamp(date.getTime()) != null) return; + try { + JSONObject data = new JSONObject(); + String enteredBy = SP.getString("careportal_enteredby", ""); + if (!enteredBy.equals("")) data.put("enteredBy", enteredBy); + data.put("created_at", DateUtil.toISOString(date)); + data.put("eventType", event); + NSUpload.uploadCareportalEntryToNS(data); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + + void processOccurenceOfAlertIntent(Intent intent) { + if (SP.getBoolean("insight_automatic_careportal_events", false)) { + Date date = getDateExtra(intent, HistoryBroadcast.EXTRA_EVENT_TIME); + String alertType = intent.getStringExtra(HistoryBroadcast.EXTRA_ALERT_TYPE); + if (MainApp.getDbHelper().getCareportalEventFromTimestamp(date.getTime()) != null) return; + logNote(date, MainApp.instance().getString(getAlertText(alertType))); + } + } + + void processPumpStatusChangedIntent(Intent intent) { + Date newStatusTime = getDateExtra(intent, HistoryBroadcast.EXTRA_EVENT_TIME); + if (SP.getBoolean("insight_automatic_careportal_events", false)) { + String newStatus = intent.getStringExtra(HistoryBroadcast.EXTRA_NEW_STATUS); + switch (newStatus) { + case "STARTED": + logNote(newStatusTime, MainApp.instance().getString(R.string.pump_started)); + break; + case "STOPPED": + logNote(newStatusTime, MainApp.instance().getString(R.string.pump_stopped)); + break; + case "PAUSED": + logNote(newStatusTime, MainApp.instance().getString(R.string.pump_paused)); + break; + } + } + if (intent.hasExtra(HistoryBroadcast.EXTRA_OLD_STATUS_TIME)) { + String oldStatus = intent.getStringExtra(HistoryBroadcast.EXTRA_OLD_STATUS); + if (oldStatus.equals("STOPPED")) { + Date oldStatusTime = getDateExtra(intent, HistoryBroadcast.EXTRA_OLD_STATUS_TIME); + int duration = (int) ((newStatusTime.getTime() - oldStatusTime.getTime()) / 60000); + + long serialNumber = Long.parseLong(intent.getStringExtra(HistoryBroadcast.EXTRA_PUMP_SERIAL_NUMBER)); + long recordId = intent.getLongExtra(HistoryBroadcast.EXTRA_EVENT_NUMBER, -1); + long uniqueRecordId = getRecordUniqueID(serialNumber, recordId); + + logAdapter.createTBRrecord(oldStatusTime, 0, duration, uniqueRecordId); + } + } + } + + private void logNote(Date date, String note) { + try { + if (MainApp.getDbHelper().getCareportalEventFromTimestamp(date.getTime()) != null) return; + JSONObject data = new JSONObject(); + String enteredBy = SP.getString("careportal_enteredby", ""); + if (!enteredBy.equals("")) data.put("enteredBy", enteredBy); + data.put("created_at", DateUtil.toISOString(date)); + data.put("eventType", CareportalEvent.NOTE); + data.put("notes", note); + NSUpload.uploadCareportalEntryToNS(data); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + private int getAlertText(String type) { + if (type.equals("Error6MechanicalError")) return R.string.alert_e6; + if (type.equals("Error7ElectronicError")) return R.string.alert_e7; + if (type.equals("Error10RewindError")) return R.string.alert_e10; + if (type.equals("Error13LanguageError")) return R.string.alert_e13; + if (type.equals("Maintenance20CartridgeNotInserted")) return R.string.alert_m20; + if (type.equals("Maintenance21CartridgeEmpty")) return R.string.alert_m21; + if (type.equals("Maintenance22BatteryEmpty")) return R.string.alert_m22; + if (type.equals("Maintenance23AutomaticOff")) return R.string.alert_m23; + if (type.equals("Maintenance24Occlusion")) return R.string.alert_m24; + if (type.equals("Maintenance25LoantimeOver")) return R.string.alert_m25; + if (type.equals("Maintenance26CartridgeChangeNotCompleted")) return R.string.alert_m26; + if (type.equals("Maintenance27DataDownloadFailed")) return R.string.alert_m27; + if (type.equals("Maintenance28PauseModeTimeout")) return R.string.alert_m28; + if (type.equals("Maintenance29BatteryTypeNotSet")) return R.string.alert_m29; + if (type.equals("Maintenance30CartridgeTypeNotSet")) return R.string.alert_m30; + if (type.equals("Warning31CartridgeLow")) return R.string.alert_w31; + if (type.equals("Warning32BatteryLow")) return R.string.alert_w32; + if (type.equals("Warning33InvalidDateTime")) return R.string.alert_w33; + if (type.equals("Warning34EndOfWarranty")) return R.string.alert_w34; + if (type.equals("Warning36TBRCancelled")) return R.string.alert_w36; + if (type.equals("Warning38BolusCancelled")) return R.string.alert_w38; + if (type.equals("Warning39LoantimeWarning")) return R.string.alert_w39; + return 0; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/history/HistoryLogAdapter.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/history/HistoryLogAdapter.java new file mode 100644 index 0000000000..689eb9e981 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/history/HistoryLogAdapter.java @@ -0,0 +1,88 @@ +package info.nightscout.androidaps.plugins.PumpInsight.history; + +import java.util.Date; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.db.ExtendedBolus; +import info.nightscout.androidaps.db.Source; +import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; + +/** + * Created by jamorham on 27/01/2018. + *

+ * Write to the History Log + */ + +class HistoryLogAdapter { + + private static final long MAX_TIME_DIFFERENCE = 61000; + + private static void log(String msg) { + android.util.Log.e("HISTORYLOG", msg); + } + + void createTBRrecord(Date eventDate, int percent, int duration, long record_id) { + + TemporaryBasal temporaryBasal = new TemporaryBasal().date(eventDate.getTime()); + + final TemporaryBasal temporaryBasalFromHistory = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(eventDate.getTime()); + + if (temporaryBasalFromHistory == null) { + log("Create new TBR: " + eventDate + " " + percent + " " + duration); + } else { + log("Loaded existing TBR record: " + temporaryBasalFromHistory.toString()); + if (Math.abs(eventDate.getTime() - temporaryBasalFromHistory.date) < MAX_TIME_DIFFERENCE) { + if (temporaryBasalFromHistory.source != Source.PUMP) { + if (temporaryBasalFromHistory.percentRate == percent) { + log("Things seem to match: %" + percent); + temporaryBasal = temporaryBasalFromHistory; + MainApp.getDbHelper().delete(temporaryBasalFromHistory); + } else { + log("This record has different percent rates: " + temporaryBasalFromHistory.percentRate + " vs us: " + percent); + } + } else { + log("This record is already a pump record!"); + } + } else { + log("Time difference too great! : " + (eventDate.getTime() - temporaryBasalFromHistory.date)); + } + } + + temporaryBasal.source(Source.PUMP) + .pumpId(record_id) + .percent(percent) + .duration(duration); + + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(temporaryBasal); + } + + void createExtendedBolusRecord(Date eventDate, double insulin, int durationInMinutes, long record_id) { + + // TODO trap items below minimum period + + final ExtendedBolus extendedBolus = new ExtendedBolus(); + extendedBolus.date = eventDate.getTime(); + extendedBolus.insulin = insulin; + extendedBolus.durationInMinutes = durationInMinutes; + extendedBolus.source = Source.PUMP; + extendedBolus.pumpId = record_id; + + TreatmentsPlugin.getPlugin().addToHistoryExtendedBolus(extendedBolus); + } + + void createStandardBolusRecord(Date eventDate, double insulin, long record_id) { + + //DetailedBolusInfo detailedBolusInfo = DetailedBolusInfoStorage.findDetailedBolusInfo(eventDate.getTime()); + + // TODO do we need to do the same delete + insert that we are doing for temporary basals here too? + + final DetailedBolusInfo detailedBolusInfo = new DetailedBolusInfo(); + detailedBolusInfo.date = eventDate.getTime(); + detailedBolusInfo.source = Source.PUMP; + detailedBolusInfo.pumpId = record_id; + detailedBolusInfo.insulin = insulin; + TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/history/HistoryReceiver.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/history/HistoryReceiver.java new file mode 100644 index 0000000000..efce8d7a40 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/history/HistoryReceiver.java @@ -0,0 +1,138 @@ +package info.nightscout.androidaps.plugins.PumpInsight.history; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; + +import static info.nightscout.androidaps.plugins.PumpInsight.history.HistoryReceiver.Status.BUSY; +import static info.nightscout.androidaps.plugins.PumpInsight.history.HistoryReceiver.Status.SYNCED; +import static info.nightscout.androidaps.plugins.PumpInsight.history.HistoryReceiver.Status.SYNCING; +import static sugar.free.sightparser.handling.HistoryBroadcast.*; + +/** + * Created by jamorham on 27/01/2018. + */ + +public class HistoryReceiver { + + private static BroadcastReceiver historyReceiver; + private volatile static Status status = Status.IDLE; + private volatile HistoryIntentAdapter intentAdapter; + + public HistoryReceiver() { + initializeHistoryReceiver(); + } + + public static synchronized void registerHistoryReceiver() { + try { + MainApp.instance().unregisterReceiver(historyReceiver); + } catch (Exception e) { + // + } + + final IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_PUMP_STATUS_CHANGED); + filter.addAction(ACTION_BOLUS_PROGRAMMED); + filter.addAction(ACTION_BOLUS_DELIVERED); + filter.addAction(ACTION_END_OF_TBR); + filter.addAction(ACTION_DAILY_TOTAL); + filter.addAction(ACTION_SYNC_STARTED); + filter.addAction(ACTION_STILL_SYNCING); + filter.addAction(ACTION_SYNC_FINISHED); + filter.addAction(ACTION_CANNULA_FILLED); + filter.addAction(ACTION_CARTRIDGE_INSERTED); + filter.addAction(ACTION_BATTERY_INSERTED); + filter.addAction(ACTION_OCCURENCE_OF_ALERT); + filter.addAction(ACTION_PUMP_STATUS_CHANGED); + + MainApp.instance().registerReceiver(historyReceiver, filter); + } + + // History + + private static void log(String msg) { + android.util.Log.e("INSIGHTPUMPHR", msg); + } + + public static String getStatusString() { + return status.toString(); + } + + private synchronized void initializeHistoryReceiver() { + historyReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, final Intent intent) { + + final String action = intent.getAction(); + if (action == null) return; + + if (intentAdapter == null) { + synchronized (this) { + if (intentAdapter == null) { + intentAdapter = new HistoryIntentAdapter(); + } + } + } + + switch (action) { + case ACTION_SYNC_STARTED: + status = SYNCING; + break; + case ACTION_STILL_SYNCING: + status = BUSY; + break; + case ACTION_SYNC_FINISHED: + status = SYNCED; + break; + case ACTION_BOLUS_DELIVERED: + intentAdapter.processDeliveredBolusIntent(intent); + break; + case ACTION_END_OF_TBR: + intentAdapter.processTBRIntent(intent); + break; + case ACTION_DAILY_TOTAL: + intentAdapter.processDailyTotalIntent(intent); + break; + case ACTION_CANNULA_FILLED: + intentAdapter.processCannulaFilledIntent(intent); + break; + case ACTION_CARTRIDGE_INSERTED: + intentAdapter.processCartridgeInsertedIntent(intent); + break; + case ACTION_BATTERY_INSERTED: + intentAdapter.processBatteryInsertedIntent(intent); + break; + case ACTION_OCCURENCE_OF_ALERT: + intentAdapter.processOccurenceOfAlertIntent(intent); + break; + case ACTION_PUMP_STATUS_CHANGED: + intentAdapter.processPumpStatusChangedIntent(intent); + break; + } + } + }; + } + + enum Status { + IDLE(R.string.insight_history_idle), + SYNCING(R.string.insight_history_syncing), + BUSY(R.string.insight_history_busy), + SYNCED(R.string.insight_history_synced); + + private final int string_id; + + Status(int string_id) { + this.string_id = string_id; + } + + @Override + public String toString() { + return MainApp.instance().getString(string_id); + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/history/LiveHistory.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/history/LiveHistory.java new file mode 100644 index 0000000000..ca3848e113 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/history/LiveHistory.java @@ -0,0 +1,37 @@ +package info.nightscout.androidaps.plugins.PumpInsight.history; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.PumpInsight.utils.Helpers; + +/** + * Created by jamorham on 27/01/2018. + * + * In memory status storage class + */ + +public class LiveHistory { + + private static String status = ""; + private static long status_time = -1; + + public static String getStatus() { + if (status.equals("")) return status; + return status + " " + Helpers.niceTimeScalar(Helpers.msSince(status_time)) + " " + gs(R.string.ago); + } + + public static long getStatusTime() { + return status_time; + } + + static void setStatus(String mystatus, long eventtime) { + if (eventtime > status_time) { + status_time = eventtime; + status = mystatus; + } + } + + private static String gs(int id) { + return MainApp.instance().getString(id); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/history/PumpIdCache.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/history/PumpIdCache.java new file mode 100644 index 0000000000..940a7af3cf --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/history/PumpIdCache.java @@ -0,0 +1,33 @@ +package info.nightscout.androidaps.plugins.PumpInsight.history; + +import info.nightscout.utils.SP; + +/** + * Created by jamorham on 01/02/2018. + */ + +public class PumpIdCache { + + private static final String INSIGHT_PUMP_ID_PREF = "insight-pump-id"; + private static long cachedPumpSerialNumber = -1; + + private static void log(String msg) { + android.util.Log.e("PumpIdCache", msg); + } + + static void updatePumpSerialNumber(long pump_serial_number) { + if (pump_serial_number != cachedPumpSerialNumber) { + cachedPumpSerialNumber = pump_serial_number; + log("Updating pump serial number: " + pump_serial_number); + SP.putLong(INSIGHT_PUMP_ID_PREF, cachedPumpSerialNumber); + } + } + + public static long getRecordUniqueID(long record_id) { + if (cachedPumpSerialNumber == -1) { + cachedPumpSerialNumber = SP.getLong(INSIGHT_PUMP_ID_PREF, 0L); + } + return HistoryIntentAdapter.getRecordUniqueID(cachedPumpSerialNumber, record_id); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/utils/Helpers.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/utils/Helpers.java new file mode 100644 index 0000000000..a2abda40d7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/utils/Helpers.java @@ -0,0 +1,197 @@ +package info.nightscout.androidaps.plugins.PumpInsight.utils; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Handler; +import android.os.PowerManager; +import android.util.Log; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.HashMap; +import java.util.Map; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; + +/** + * Created by jamorham on 24/01/2018. + * + * Useful utility methods from xDrip+ + * + */ + +public class Helpers { + + private static final String TAG = "InsightHelpers"; + + private static final Map rateLimits = new HashMap<>(); + // singletons to avoid repeated allocation + private static DecimalFormatSymbols dfs; + private static DecimalFormat df; + + // return true if below rate limit + public static synchronized boolean ratelimit(String name, int seconds) { + // check if over limit + if ((rateLimits.containsKey(name)) && (tsl() - rateLimits.get(name) < (seconds * 1000))) { + Log.d(TAG, name + " rate limited: " + seconds + " seconds"); + return false; + } + // not over limit + rateLimits.put(name, tsl()); + return true; + } + + public static long tsl() { + return System.currentTimeMillis(); + } + + public static long msSince(long when) { + return (tsl() - when); + } + + public static long msTill(long when) { + return (when - tsl()); + } + + public static boolean checkPackageExists(Context context, String TAG, String packageName) { + try { + final PackageManager pm = context.getPackageManager(); + final PackageInfo pi = pm.getPackageInfo(packageName, 0); + return pi.packageName.equals(packageName); + } catch (PackageManager.NameNotFoundException e) { + return false; + } catch (Exception e) { + Log.wtf(TAG, "Exception trying to determine packages! " + e); + return false; + } + } + + public static boolean runOnUiThreadDelayed(Runnable theRunnable, long delay) { + return new Handler(MainApp.instance().getMainLooper()).postDelayed(theRunnable, delay); + } + + public static PowerManager.WakeLock getWakeLock(final String name, int millis) { + final PowerManager pm = (PowerManager) MainApp.instance().getSystemService(Context.POWER_SERVICE); + if (pm == null) return null; + final PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name); + wl.acquire(millis); + return wl; + } + + public static void releaseWakeLock(PowerManager.WakeLock wl) { + if (wl == null) return; + if (wl.isHeld()) wl.release(); + } + + public static String niceTimeSince(long t) { + return niceTimeScalar(msSince(t)); + } + + public static String niceTimeTill(long t) { + return niceTimeScalar(-msSince(t)); + } + + public static String niceTimeScalar(long t) { + String unit = gs(R.string.second); + t = t / 1000; + if (t > 59) { + unit = gs(R.string.minute); + t = t / 60; + if (t > 59) { + unit = gs(R.string.hour); + t = t / 60; + if (t > 24) { + unit = gs(R.string.day); + t = t / 24; + if (t > 28) { + unit = gs(R.string.week); + t = t / 7; + } + } + } + } + if (t != 1) unit = unit + gs(R.string.time_plural); + return qs((double) t, 0) + " " + unit; + } + + private static String gs(int id) { + return MainApp.instance().getString(id); + } + + public static String qs(double x, int digits) { + + if (digits == -1) { + digits = 0; + if (((int) x != x)) { + digits++; + if ((((int) x * 10) / 10 != x)) { + digits++; + if ((((int) x * 100) / 100 != x)) digits++; + } + } + } + + if (dfs == null) { + final DecimalFormatSymbols local_dfs = new DecimalFormatSymbols(); + local_dfs.setDecimalSeparator('.'); + dfs = local_dfs; // avoid race condition + } + + final DecimalFormat this_df; + // use singleton if on ui thread otherwise allocate new as DecimalFormat is not thread safe + if (Thread.currentThread().getId() == 1) { + if (df == null) { + final DecimalFormat local_df = new DecimalFormat("#", dfs); + local_df.setMinimumIntegerDigits(1); + df = local_df; // avoid race condition + } + this_df = df; + } else { + this_df = new DecimalFormat("#", dfs); + } + + this_df.setMaximumFractionDigits(digits); + return this_df.format(x); + } + + public static String niceTimeScalarRedux(long t) { + return niceTimeScalar(t).replaceFirst("^1 ", ""); + } + + public static String niceTimeScalarBrief(long t) { + // TODO i18n wont work for non-latin characterset + return niceTimeScalar(t).replaceFirst("([a-z])[a-z]*", "$1").replace(" ",""); + } + + public static String hourMinuteString(long timestamp) { + return android.text.format.DateFormat.format("kk:mm", timestamp).toString(); + } + + public static String hourMinuteSecondString(long timestamp) { + return android.text.format.DateFormat.format("kk:mm:ss", timestamp).toString(); + } + + public static String dateTimeText(long timestamp) { + return android.text.format.DateFormat.format("yyyy-MM-dd kk:mm:ss", timestamp).toString(); + } + + public static String dateText(long timestamp) { + return android.text.format.DateFormat.format("yyyy-MM-dd", timestamp).toString(); + } + + public static String capitalize(String text) { + return text.substring(0, 1).toUpperCase() + text.substring(1).toLowerCase(); + } + + public static double roundDouble(double value, int places) { + if (places < 0) throw new IllegalArgumentException("Invalid decimal places"); + BigDecimal bd = new BigDecimal(value); + bd = bd.setScale(places, RoundingMode.HALF_UP); + return bd.doubleValue(); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/utils/StatusItem.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/utils/StatusItem.java new file mode 100644 index 0000000000..395dd51084 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/utils/StatusItem.java @@ -0,0 +1,64 @@ +package info.nightscout.androidaps.plugins.PumpInsight.utils; + +/** + * Created by jamorham on 26/01/2018. + * + * For representing row status items + */ + +public class StatusItem { + + public enum Highlight { + NORMAL, + GOOD, + BAD, + NOTICE, + CRITICAL + } + + public String name; + public String value; + public Highlight highlight; + public String button_name; + public Runnable runnable; + + + public StatusItem(String name, String value) { + this(name, value, Highlight.NORMAL); + } + + public StatusItem() { + this("line-break", "", Highlight.NORMAL); + } + + public StatusItem(String name, Highlight highlight) { + this("heading-break", name, highlight); + } + + public StatusItem(String name, Runnable runnable) { + this("button-break", "", Highlight.NORMAL, name, runnable); + } + + public StatusItem(String name, String value, Highlight highlight) { + this(name, value, highlight, null, null); + } + + public StatusItem(String name, String value, Highlight highlight, String button_name, Runnable runnable) { + this.name = name; + this.value = value; + this.highlight = highlight; + this.button_name = button_name; + this.runnable = runnable; + } + + public StatusItem(String name, Integer value) { + this(name, value, Highlight.NORMAL); + } + + public StatusItem(String name, Integer value, Highlight highlight) { + this.name = name; + this.value = Integer.toString(value); + this.highlight = highlight; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/utils/ui/StatusItemViewAdapter.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/utils/ui/StatusItemViewAdapter.java new file mode 100644 index 0000000000..87c40c19de --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpInsight/utils/ui/StatusItemViewAdapter.java @@ -0,0 +1,85 @@ +package info.nightscout.androidaps.plugins.PumpInsight.utils.ui; + +import android.app.Activity; +import android.graphics.Color; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.PumpInsight.utils.StatusItem; + +/** + * Created by jamorham on 26/01/2018. + * + * Convert StatusItem to View + */ + +public class StatusItemViewAdapter { + + private final Activity activity; + private final ViewGroup holder; + + public StatusItemViewAdapter(Activity activity, ViewGroup holder) { + this.activity = activity; + this.holder = holder; + } + + public View inflateStatus(StatusItem statusItem) { + if (activity == null) return null; + final View child = activity.getLayoutInflater().inflate(R.layout.insightpump_statuselements, null); + final TextView name = (TextView) child.findViewById(R.id.insightstatuslabel); + final TextView value = (TextView)child.findViewById(R.id.insightstatusvalue); + final TextView spacer = (TextView)child.findViewById(R.id.insightstatusspacer); + final LinearLayout layout = (LinearLayout)child.findViewById(R.id.insightstatuslayout); + + if (statusItem.name.equals("line-break")) { + spacer.setVisibility(View.GONE); + name.setVisibility(View.GONE); + value.setVisibility(View.GONE); + layout.setPadding(10, 10, 10, 10); + } else if (statusItem.name.equals("heading-break")) { + value.setVisibility(View.GONE); + spacer.setVisibility(View.GONE); + name.setText(statusItem.value); + name.setGravity(Gravity.CENTER_HORIZONTAL); + name.setTextColor(Color.parseColor("#fff9c4")); + } else { + name.setText(statusItem.name); + value.setText(statusItem.value); + } + + final int this_color = getHighlightColor(statusItem); + name.setBackgroundColor(this_color); + value.setBackgroundColor(this_color); + spacer.setBackgroundColor(this_color); + + if (this_color != Color.TRANSPARENT) { + name.setTextColor(Color.WHITE); + spacer.setTextColor(Color.WHITE); + } + + if (holder != null) { + holder.addView(child); + } + return child; + } + + private static int getHighlightColor(StatusItem row) { + switch (row.highlight) { + case BAD: + return Color.parseColor("#480000"); + case NOTICE: + return Color.parseColor("#403000"); + case GOOD: + return Color.parseColor("#003000"); + case CRITICAL: + return Color.parseColor("#770000"); + default: + return Color.TRANSPARENT; + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpMDI/MDIPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMDI/MDIPlugin.java index 564d29d53b..9c19a383ae 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpMDI/MDIPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpMDI/MDIPlugin.java @@ -15,21 +15,19 @@ import info.nightscout.androidaps.data.DetailedBolusInfo; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.PumpDescription; import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.DateUtil; /** * Created by mike on 05.08.2016. */ -public class MDIPlugin implements PluginBase, PumpInterface { +public class MDIPlugin extends PluginBase implements PumpInterface { private static Logger log = LoggerFactory.getLogger(MDIPlugin.class); - private boolean fragmentEnabled = false; - private boolean fragmentVisible = false; - - private PumpDescription pumpDescription = new PumpDescription(); - private static MDIPlugin plugin = null; public static MDIPlugin getPlugin() { @@ -38,7 +36,13 @@ public class MDIPlugin implements PluginBase, PumpInterface { return plugin; } + private PumpDescription pumpDescription = new PumpDescription(); + private MDIPlugin() { + super(new PluginDescription() + .mainType(PluginType.PUMP) + .pluginName(R.string.mdi) + ); pumpDescription.isBolusCapable = true; pumpDescription.bolusStep = 0.5d; @@ -48,72 +52,18 @@ public class MDIPlugin implements PluginBase, PumpInterface { pumpDescription.isRefillingCapable = false; } - @Override - public String getFragmentClass() { - return null; - } - - @Override - public String getName() { - return MainApp.instance().getString(R.string.mdi); - } - - @Override - public String getNameShort() { - // use long name as fallback (not visible in tabs) - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == PUMP && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return false; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return false; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == PUMP) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == PUMP) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return -1; - } - - @Override - public int getType() { - return PluginBase.PUMP; - } - @Override public boolean isFakingTempsByExtendedBoluses() { return false; } + @Override + public PumpEnactResult loadTDDs() { + //no result, could read DB in the future? + PumpEnactResult result = new PumpEnactResult(); + return result; + } + @Override public boolean isInitialized() { return true; @@ -185,7 +135,7 @@ public class MDIPlugin implements PluginBase, PumpInterface { result.bolusDelivered = detailedBolusInfo.insulin; result.carbsDelivered = detailedBolusInfo.carbs; result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); + TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo); return result; } @@ -194,7 +144,7 @@ public class MDIPlugin implements PluginBase, PumpInterface { } @Override - public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean enforceNew) { + public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, Profile profile, boolean enforceNew) { PumpEnactResult result = new PumpEnactResult(); result.success = false; result.comment = MainApp.instance().getString(R.string.pumperror); @@ -204,7 +154,7 @@ public class MDIPlugin implements PluginBase, PumpInterface { } @Override - public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, boolean enforceNew) { + public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, Profile profile, boolean enforceNew) { PumpEnactResult result = new PumpEnactResult(); result.success = false; result.comment = MainApp.instance().getString(R.string.pumperror); @@ -244,7 +194,8 @@ public class MDIPlugin implements PluginBase, PumpInterface { } @Override - public JSONObject getJSONStatus() { + public JSONObject getJSONStatus(Profile profile, String profileName) { + long now = System.currentTimeMillis(); JSONObject pump = new JSONObject(); JSONObject status = new JSONObject(); JSONObject extended = new JSONObject(); @@ -252,14 +203,14 @@ public class MDIPlugin implements PluginBase, PumpInterface { status.put("status", "normal"); extended.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION); try { - extended.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName()); + extended.put("ActiveProfile", profileName); } catch (Exception e) { } - status.put("timestamp", DateUtil.toISOString(new Date())); + status.put("timestamp", DateUtil.toISOString(now)); pump.put("status", status); pump.put("extended", extended); - pump.put("clock", DateUtil.toISOString(new Date())); + pump.put("clock", DateUtil.toISOString(now)); } catch (JSONException e) { } return pump; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpFragment.java index a5c2b4dd93..5a42af3811 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpFragment.java @@ -10,17 +10,19 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; import com.squareup.otto.Subscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.db.ExtendedBolus; +import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; import info.nightscout.androidaps.plugins.PumpCommon.defs.PumpType; import info.nightscout.androidaps.plugins.PumpVirtual.events.EventVirtualPumpUpdateGui; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; +import info.nightscout.utils.FabricPrivacy; public class VirtualPumpFragment extends SubscriberFragment { private static Logger log = LoggerFactory.getLogger(VirtualPumpFragment.class); @@ -67,7 +69,7 @@ public class VirtualPumpFragment extends SubscriberFragment { return view; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } return null; @@ -87,13 +89,15 @@ public class VirtualPumpFragment extends SubscriberFragment { public void run() { VirtualPumpPlugin virtualPump = VirtualPumpPlugin.getPlugin(); basaBasalRateView.setText(virtualPump.getBaseBasalRate() + "U"); - if (MainApp.getConfigBuilder().isTempBasalInProgress()) { - tempBasalView.setText(MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()).toStringFull()); + TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); + if (activeTemp != null) { + tempBasalView.setText(activeTemp.toStringFull()); } else { tempBasalView.setText(""); } - if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) { - extendedBolusView.setText(MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()).toString()); + ExtendedBolus activeExtendedBolus = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(System.currentTimeMillis()); + if (activeExtendedBolus != null) { + extendedBolusView.setText(activeExtendedBolus.toString()); } else { extendedBolusView.setText(""); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpPlugin.java index 57f4d5fe79..e129786a58 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpPlugin.java @@ -20,15 +20,17 @@ import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.PumpDescription; import info.nightscout.androidaps.interfaces.PumpInterface; -import info.nightscout.androidaps.interfaces.TreatmentsInterface; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress; import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.PumpCommon.defs.PumpType; import info.nightscout.androidaps.plugins.PumpCommon.utils.PumpUtil; import info.nightscout.androidaps.plugins.PumpVirtual.events.EventVirtualPumpUpdateGui; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.DateUtil; import info.nightscout.utils.NSUpload; import info.nightscout.utils.SP; @@ -36,39 +38,9 @@ import info.nightscout.utils.SP; /** * Created by mike on 05.08.2016. */ -public class VirtualPumpPlugin implements PluginBase, PumpInterface { +public class VirtualPumpPlugin extends PluginBase implements PumpInterface { private static Logger log = LoggerFactory.getLogger(VirtualPumpPlugin.class); - public static Double defaultBasalValue = 0.2d; - - static Integer batteryPercent = 50; - static Integer reservoirInUnits = 50; - - private Date lastDataTime = new Date(0); - - private boolean fragmentEnabled = true; - private boolean fragmentVisible = true; - - private static boolean fromNSAreCommingFakedExtendedBoluses = false; - - private PumpDescription pumpDescription = new PumpDescription(); - - PumpType pumpType = null; - - - private static void loadFakingStatus() { - fromNSAreCommingFakedExtendedBoluses = SP.getBoolean("fromNSAreCommingFakedExtendedBoluses", false); - } - - public static void setFakingStatus(boolean newStatus) { - fromNSAreCommingFakedExtendedBoluses = newStatus; - SP.putBoolean("fromNSAreCommingFakedExtendedBoluses", fromNSAreCommingFakedExtendedBoluses); - } - - public static boolean getFakingStatus() { - return fromNSAreCommingFakedExtendedBoluses; - } - private static VirtualPumpPlugin plugin = null; public static VirtualPumpPlugin getPlugin() { @@ -78,7 +50,39 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { return plugin; } - private VirtualPumpPlugin() { + static Integer batteryPercent = 50; + static Integer reservoirInUnits = 50; + + private Date lastDataTime = new Date(0); + + private static boolean fromNSAreCommingFakedExtendedBoluses = false; + + private PumpDescription pumpDescription = new PumpDescription(); + + PumpType pumpType = null; + + + private static void loadFakingStatus() { + fromNSAreCommingFakedExtendedBoluses = SP.getBoolean(R.string.key_fromNSAreCommingFakedExtendedBoluses, false); + } + + public static void setFakingStatus(boolean newStatus) { + fromNSAreCommingFakedExtendedBoluses = newStatus; + SP.putBoolean(R.string.key_fromNSAreCommingFakedExtendedBoluses, fromNSAreCommingFakedExtendedBoluses); + } + + public static boolean getFakingStatus() { + return fromNSAreCommingFakedExtendedBoluses; + } + + public VirtualPumpPlugin() { + super(new PluginDescription() + .mainType(PluginType.PUMP) + .fragmentClass(VirtualPumpFragment.class.getName()) + .pluginName(R.string.virtualpump) + .shortName(R.string.virtualpump_shortname) + .preferencesId(R.xml.pref_virtualpump) + ); pumpDescription.isBolusCapable = true; pumpDescription.bolusStep = 0.1d; @@ -94,6 +98,8 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { pumpDescription.tempPercentStep = 10; pumpDescription.tempDurationStep = 30; + pumpDescription.tempDurationStep15mAllowed = true; + pumpDescription.tempDurationStep30mAllowed = true; pumpDescription.tempMaxDuration = 24 * 60; @@ -101,73 +107,10 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { pumpDescription.basalStep = 0.01d; pumpDescription.basalMinimumRate = 0.01d; - pumpDescription.isRefillingCapable = false; - } + pumpDescription.isRefillingCapable = true; - @Override - public String getFragmentClass() { - return VirtualPumpFragment.class.getName(); - } - - @Override - public String getName() { - return MainApp.instance().getString(R.string.virtualpump); - } - - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.virtualpump_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == PUMP && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == PUMP && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == PUMP) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == PUMP) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return R.xml.pref_virtualpump; - } - - @Override - public int getType() { - return PluginBase.PUMP; + pumpDescription.storesCarbInfo = false; + pumpDescription.is30minBasalRatesCapable = true; } @Override @@ -175,6 +118,13 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { return (Config.NSCLIENT || Config.G5UPLOADER) && fromNSAreCommingFakedExtendedBoluses; } + @Override + public PumpEnactResult loadTDDs() { + //no result, could read DB in the future? + PumpEnactResult result = new PumpEnactResult(); + return result; + } + @Override public boolean isInitialized() { return true; @@ -245,7 +195,7 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { public double getBaseBasalRate() { Profile profile = MainApp.getConfigBuilder().getProfile(); if (profile != null) - return profile.getBasal() != null ? profile.getBasal() : 0d; + return profile.getBasal(); else return 0d; } @@ -279,7 +229,7 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { log.debug("Delivering treatment insulin: " + detailedBolusInfo.insulin + "U carbs: " + detailedBolusInfo.carbs + "g " + result); MainApp.bus().post(new EventVirtualPumpUpdateGui()); lastDataTime = new Date(); - MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); + TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo); return result; } @@ -289,14 +239,12 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { } @Override - public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean enforceNew) { - TreatmentsInterface treatmentsInterface = MainApp.getConfigBuilder(); - TemporaryBasal tempBasal = new TemporaryBasal(); - tempBasal.date = System.currentTimeMillis(); - tempBasal.isAbsolute = true; - tempBasal.absoluteRate = absoluteRate; - tempBasal.durationInMinutes = durationInMinutes; - tempBasal.source = Source.USER; + public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, Profile profile, boolean enforceNew) { + TemporaryBasal tempBasal = new TemporaryBasal() + .date(System.currentTimeMillis()) + .absolute(absoluteRate) + .duration(durationInMinutes) + .source(Source.USER); PumpEnactResult result = new PumpEnactResult(); result.success = true; result.enacted = true; @@ -304,7 +252,7 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { result.absolute = absoluteRate; result.duration = durationInMinutes; result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - treatmentsInterface.addToHistoryTempBasal(tempBasal); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempBasal); if (Config.logPumpComm) log.debug("Setting temp basal absolute: " + result); MainApp.bus().post(new EventVirtualPumpUpdateGui()); @@ -313,20 +261,18 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { } @Override - public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, boolean enforceNew) { - TreatmentsInterface treatmentsInterface = MainApp.getConfigBuilder(); + public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, Profile profile, boolean enforceNew) { PumpEnactResult result = new PumpEnactResult(); - if (MainApp.getConfigBuilder().isTempBasalInProgress()) { + if (TreatmentsPlugin.getPlugin().isTempBasalInProgress()) { result = cancelTempBasal(false); if (!result.success) return result; } - TemporaryBasal tempBasal = new TemporaryBasal(); - tempBasal.date = System.currentTimeMillis(); - tempBasal.isAbsolute = false; - tempBasal.percentRate = percent; - tempBasal.durationInMinutes = durationInMinutes; - tempBasal.source = Source.USER; + TemporaryBasal tempBasal = new TemporaryBasal() + .date(System.currentTimeMillis()) + .percent(percent) + .duration(durationInMinutes) + .source(Source.USER); result.success = true; result.enacted = true; result.percent = percent; @@ -334,7 +280,7 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { result.isTempCancel = false; result.duration = durationInMinutes; result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - treatmentsInterface.addToHistoryTempBasal(tempBasal); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempBasal); if (Config.logPumpComm) log.debug("Settings temp basal percent: " + result); MainApp.bus().post(new EventVirtualPumpUpdateGui()); @@ -344,7 +290,6 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { @Override public PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes) { - TreatmentsInterface treatmentsInterface = MainApp.getConfigBuilder(); PumpEnactResult result = cancelExtendedBolus(); if (!result.success) return result; @@ -359,7 +304,7 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { result.isTempCancel = false; result.duration = durationInMinutes; result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - treatmentsInterface.addToHistoryExtendedBolus(extendedBolus); + TreatmentsPlugin.getPlugin().addToHistoryExtendedBolus(extendedBolus); if (Config.logPumpComm) log.debug("Setting extended bolus: " + result); MainApp.bus().post(new EventVirtualPumpUpdateGui()); @@ -369,16 +314,14 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { @Override public PumpEnactResult cancelTempBasal(boolean force) { - TreatmentsInterface treatmentsInterface = MainApp.getConfigBuilder(); PumpEnactResult result = new PumpEnactResult(); result.success = true; result.isTempCancel = true; result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - if (treatmentsInterface.isTempBasalInProgress()) { + if (TreatmentsPlugin.getPlugin().isTempBasalInProgress()) { result.enacted = true; - TemporaryBasal tempStop = new TemporaryBasal(System.currentTimeMillis()); - tempStop.source = Source.USER; - treatmentsInterface.addToHistoryTempBasal(tempStop); + TemporaryBasal tempStop = new TemporaryBasal().date(System.currentTimeMillis()).source(Source.USER); + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempStop); //tempBasal = null; if (Config.logPumpComm) log.debug("Canceling temp basal: " + result); @@ -390,12 +333,11 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { @Override public PumpEnactResult cancelExtendedBolus() { - TreatmentsInterface treatmentsInterface = MainApp.getConfigBuilder(); PumpEnactResult result = new PumpEnactResult(); - if (treatmentsInterface.isInHistoryExtendedBoluslInProgress()) { + if (TreatmentsPlugin.getPlugin().isInHistoryExtendedBoluslInProgress()) { ExtendedBolus exStop = new ExtendedBolus(System.currentTimeMillis()); exStop.source = Source.USER; - treatmentsInterface.addToHistoryExtendedBolus(exStop); + TreatmentsPlugin.getPlugin().addToHistoryExtendedBolus(exStop); } result.success = true; result.enacted = true; @@ -409,7 +351,8 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { } @Override - public JSONObject getJSONStatus() { + public JSONObject getJSONStatus(Profile profile, String profileName) { + long now = System.currentTimeMillis(); if (!SP.getBoolean("virtualpump_uploadstatus", false)) { return null; } @@ -422,28 +365,28 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { status.put("status", "normal"); extended.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION); try { - extended.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName()); + extended.put("ActiveProfile", profileName); } catch (Exception e) { } - TemporaryBasal tb = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); + TemporaryBasal tb = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(now); if (tb != null) { - extended.put("TempBasalAbsoluteRate", tb.tempBasalConvertedToAbsolute(System.currentTimeMillis())); + extended.put("TempBasalAbsoluteRate", tb.tempBasalConvertedToAbsolute(now, profile)); extended.put("TempBasalStart", DateUtil.dateAndTimeString(tb.date)); extended.put("TempBasalRemaining", tb.getPlannedRemainingMinutes()); } - ExtendedBolus eb = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); + ExtendedBolus eb = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(now); if (eb != null) { extended.put("ExtendedBolusAbsoluteRate", eb.absoluteRate()); extended.put("ExtendedBolusStart", DateUtil.dateAndTimeString(eb.date)); extended.put("ExtendedBolusRemaining", eb.getPlannedRemainingMinutes()); } - status.put("timestamp", DateUtil.toISOString(new Date())); + status.put("timestamp", DateUtil.toISOString(now)); pump.put("battery", battery); pump.put("status", status); pump.put("extended", extended); pump.put("reservoir", reservoirInUnits); - pump.put("clock", DateUtil.toISOString(new Date())); + pump.put("clock", DateUtil.toISOString(now)); } catch (JSONException e) { log.error("Unhandled exception", e); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityAAPS/SensitivityAAPSPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityAAPS/SensitivityAAPSPlugin.java index f45d6cec40..28c4192f99 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityAAPS/SensitivityAAPSPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityAAPS/SensitivityAAPSPlugin.java @@ -16,6 +16,8 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.SensitivityInterface; import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensData; import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensResult; @@ -28,12 +30,9 @@ import info.nightscout.utils.SafeParse; * Created by mike on 24.06.2017. */ -public class SensitivityAAPSPlugin implements PluginBase, SensitivityInterface{ +public class SensitivityAAPSPlugin extends PluginBase implements SensitivityInterface { private static Logger log = LoggerFactory.getLogger(SensitivityAAPSPlugin.class); - private boolean fragmentEnabled = true; - private boolean fragmentVisible = false; - static SensitivityAAPSPlugin plugin = null; public static SensitivityAAPSPlugin getPlugin() { @@ -42,70 +41,18 @@ public class SensitivityAAPSPlugin implements PluginBase, SensitivityInterface{ return plugin; } - @Override - public int getType() { - return SENSITIVITY; + public SensitivityAAPSPlugin() { + super(new PluginDescription() + .mainType(PluginType.SENSITIVITY) + .pluginName(R.string.sensitivityaaps) + .shortName(R.string.sensitivity_shortname) + .preferencesId(R.xml.pref_absorption_aaps) + ); } - @Override - public String getFragmentClass() { - return null; - } - - @Override - public String getName() { - return MainApp.sResources.getString(R.string.sensitivityaaps); - } - - @Override - public String getNameShort() { - return MainApp.sResources.getString(R.string.sensitivity_shortname); - } - - @Override - public boolean isEnabled(int type) { - return type == SENSITIVITY && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == SENSITIVITY && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return false; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == SENSITIVITY) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == SENSITIVITY) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return R.xml.pref_absorption_aaps; - } - - @Override public AutosensResult detectSensitivity(long fromTime, long toTime) { - LongSparseArray autosensDataTable = IobCobCalculatorPlugin.getAutosensDataTable(); + LongSparseArray autosensDataTable = IobCobCalculatorPlugin.getPlugin().getAutosensDataTable(); String age = SP.getString(R.string.key_age, ""); int defaultHours = 24; @@ -114,12 +61,19 @@ public class SensitivityAAPSPlugin implements PluginBase, SensitivityInterface{ if (age.equals(MainApp.sResources.getString(R.string.key_child))) defaultHours = 4; int hoursForDetection = SP.getInt(R.string.key_openapsama_autosens_period, defaultHours); + Profile profile = MainApp.getConfigBuilder().getProfile(); + + if (profile == null) { + log.debug("No profile"); + return new AutosensResult(); + } + if (autosensDataTable == null || autosensDataTable.size() < 4) { log.debug("No autosens data available"); return new AutosensResult(); } - AutosensData current = IobCobCalculatorPlugin.getAutosensData(toTime); // this is running inside lock already + AutosensData current = IobCobCalculatorPlugin.getPlugin().getAutosensData(toTime); // this is running inside lock already if (current == null) { log.debug("No autosens data available"); return new AutosensResult(); @@ -159,14 +113,14 @@ public class SensitivityAAPSPlugin implements PluginBase, SensitivityInterface{ Double[] deviations = new Double[deviationsArray.size()]; deviations = deviationsArray.toArray(deviations); - Profile profile = MainApp.getConfigBuilder().getProfile(); - double sens = profile.getIsf(); String ratioLimit = ""; String sensResult = ""; - log.debug("Records: " + index + " " + pastSensitivity); + if (Config.logAutosensData) + log.debug("Records: " + index + " " + pastSensitivity); + Arrays.sort(deviations); double percentile = IobCobCalculatorPlugin.percentile(deviations, 0.50); @@ -181,11 +135,12 @@ public class SensitivityAAPSPlugin implements PluginBase, SensitivityInterface{ sensResult = "Sensitivity normal"; } - log.debug(sensResult); + if (Config.logAutosensData) + log.debug(sensResult); double rawRatio = ratio; - ratio = Math.max(ratio, SafeParse.stringToDouble(SP.getString("openapsama_autosens_min", "0.7"))); - ratio = Math.min(ratio, SafeParse.stringToDouble(SP.getString("openapsama_autosens_max", "1.2"))); + ratio = Math.max(ratio, SafeParse.stringToDouble(SP.getString(R.string.key_openapsama_autosens_min, "0.7"))); + ratio = Math.min(ratio, SafeParse.stringToDouble(SP.getString(R.string.key_openapsama_autosens_max, "1.2"))); if (ratio != rawRatio) { ratioLimit = "Ratio limited from " + rawRatio + " to " + ratio; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityOref0/SensitivityOref0Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityOref0/SensitivityOref0Plugin.java index bf7d6ce8f5..15be72880d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityOref0/SensitivityOref0Plugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityOref0/SensitivityOref0Plugin.java @@ -15,6 +15,8 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.SensitivityInterface; import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensData; import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensResult; @@ -27,12 +29,9 @@ import info.nightscout.utils.SafeParse; * Created by mike on 24.06.2017. */ -public class SensitivityOref0Plugin implements PluginBase, SensitivityInterface { +public class SensitivityOref0Plugin extends PluginBase implements SensitivityInterface { private static Logger log = LoggerFactory.getLogger(IobCobCalculatorPlugin.class); - private boolean fragmentEnabled = true; - private boolean fragmentVisible = false; - static SensitivityOref0Plugin plugin = null; public static SensitivityOref0Plugin getPlugin() { @@ -41,86 +40,40 @@ public class SensitivityOref0Plugin implements PluginBase, SensitivityInterface return plugin; } - @Override - public int getType() { - return SENSITIVITY; + public SensitivityOref0Plugin() { + super(new PluginDescription() + .mainType(PluginType.SENSITIVITY) + .pluginName(R.string.sensitivityoref0) + .shortName(R.string.sensitivity_shortname) + .preferencesId(R.xml.pref_absorption_oref0) + ); } - @Override - public String getFragmentClass() { - return null; - } - - @Override - public String getName() { - return MainApp.sResources.getString(R.string.sensitivityoref0); - } - - @Override - public String getNameShort() { - return MainApp.sResources.getString(R.string.sensitivity_shortname); - } - - @Override - public boolean isEnabled(int type) { - return type == SENSITIVITY && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == SENSITIVITY && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return false; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == SENSITIVITY) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == SENSITIVITY) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return R.xml.pref_absorption_oref0; - } - - @Override public AutosensResult detectSensitivity(long fromTime, long toTime) { - LongSparseArray autosensDataTable = IobCobCalculatorPlugin.getAutosensDataTable(); + LongSparseArray autosensDataTable = IobCobCalculatorPlugin.getPlugin().getAutosensDataTable(); String age = SP.getString(R.string.key_age, ""); int defaultHours = 24; - if (age.equals(MainApp.sResources.getString(R.string.key_adult))) defaultHours = 24; - if (age.equals(MainApp.sResources.getString(R.string.key_teenage))) defaultHours = 24; - if (age.equals(MainApp.sResources.getString(R.string.key_child))) defaultHours = 24; + if (age.equals(MainApp.gs(R.string.key_adult))) defaultHours = 24; + if (age.equals(MainApp.gs(R.string.key_teenage))) defaultHours = 24; + if (age.equals(MainApp.gs(R.string.key_child))) defaultHours = 24; int hoursForDetection = SP.getInt(R.string.key_openapsama_autosens_period, defaultHours); long now = System.currentTimeMillis(); + Profile profile = MainApp.getConfigBuilder().getProfile(); + + if (profile == null) { + log.debug("No profile"); + return new AutosensResult(); + } if (autosensDataTable == null || autosensDataTable.size() < 4) { log.debug("No autosens data available"); return new AutosensResult(); } - AutosensData current = IobCobCalculatorPlugin.getAutosensData(toTime); // this is running inside lock already + AutosensData current = IobCobCalculatorPlugin.getPlugin().getAutosensData(toTime); // this is running inside lock already if (current == null) { log.debug("No current autosens data available"); return new AutosensResult(); @@ -159,20 +112,21 @@ public class SensitivityOref0Plugin implements PluginBase, SensitivityInterface Double[] deviations = new Double[deviationsArray.size()]; deviations = deviationsArray.toArray(deviations); - Profile profile = MainApp.getConfigBuilder().getProfile(); - double sens = profile.getIsf(); double ratio = 1; String ratioLimit = ""; String sensResult = ""; - log.debug("Records: " + index + " " + pastSensitivity); + if (Config.logAutosensData) + log.debug("Records: " + index + " " + pastSensitivity); + Arrays.sort(deviations); for (double i = 0.9; i > 0.1; i = i - 0.02) { if (IobCobCalculatorPlugin.percentile(deviations, (i + 0.02)) >= 0 && IobCobCalculatorPlugin.percentile(deviations, i) < 0) { - log.debug(Math.round(100 * i) + "% of non-meal deviations negative (target 45%-50%)"); + if (Config.logAutosensData) + log.debug(Math.round(100 * i) + "% of non-meal deviations negative (target 45%-50%)"); } } double pSensitive = IobCobCalculatorPlugin.percentile(deviations, 0.50); @@ -189,12 +143,15 @@ public class SensitivityOref0Plugin implements PluginBase, SensitivityInterface } else { sensResult = "Sensitivity normal"; } - log.debug(sensResult); + + if (Config.logAutosensData) + log.debug(sensResult); + ratio = 1 + (basalOff / profile.getMaxDailyBasal()); double rawRatio = ratio; - ratio = Math.max(ratio, SafeParse.stringToDouble(SP.getString("openapsama_autosens_min", "0.7"))); - ratio = Math.min(ratio, SafeParse.stringToDouble(SP.getString("openapsama_autosens_max", "1.2"))); + ratio = Math.max(ratio, SafeParse.stringToDouble(SP.getString(R.string.key_openapsama_autosens_min, "0.7"))); + ratio = Math.min(ratio, SafeParse.stringToDouble(SP.getString(R.string.key_openapsama_autosens_max, "1.2"))); if (ratio != rawRatio) { ratioLimit = "Ratio limited from " + rawRatio + " to " + ratio; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityWeightedAverage/SensitivityWeightedAveragePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityWeightedAverage/SensitivityWeightedAveragePlugin.java index 5f9e996a8a..2f59769f55 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityWeightedAverage/SensitivityWeightedAveragePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityWeightedAverage/SensitivityWeightedAveragePlugin.java @@ -12,6 +12,8 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.SensitivityInterface; import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensData; import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensResult; @@ -24,12 +26,9 @@ import info.nightscout.utils.SafeParse; * Created by mike on 24.06.2017. */ -public class SensitivityWeightedAveragePlugin implements PluginBase, SensitivityInterface { +public class SensitivityWeightedAveragePlugin extends PluginBase implements SensitivityInterface { private static Logger log = LoggerFactory.getLogger(SensitivityWeightedAveragePlugin.class); - private boolean fragmentEnabled = true; - private boolean fragmentVisible = false; - private static SensitivityWeightedAveragePlugin plugin = null; public static SensitivityWeightedAveragePlugin getPlugin() { @@ -38,70 +37,18 @@ public class SensitivityWeightedAveragePlugin implements PluginBase, Sensitivity return plugin; } - @Override - public int getType() { - return SENSITIVITY; + public SensitivityWeightedAveragePlugin() { + super(new PluginDescription() + .mainType(PluginType.SENSITIVITY) + .pluginName(R.string.sensitivityweightedaverage) + .shortName(R.string.sensitivity_shortname) + .preferencesId(R.xml.pref_absorption_aaps) + ); } - @Override - public String getFragmentClass() { - return null; - } - - @Override - public String getName() { - return MainApp.sResources.getString(R.string.sensitivityweightedaverage); - } - - @Override - public String getNameShort() { - return MainApp.sResources.getString(R.string.sensitivity_shortname); - } - - @Override - public boolean isEnabled(int type) { - return type == SENSITIVITY && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == SENSITIVITY && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return false; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == SENSITIVITY) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == SENSITIVITY) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return R.xml.pref_absorption_aaps; - } - - @Override public AutosensResult detectSensitivity(long fromTime, long toTime) { - LongSparseArray autosensDataTable = IobCobCalculatorPlugin.getAutosensDataTable(); + LongSparseArray autosensDataTable = IobCobCalculatorPlugin.getPlugin().getAutosensDataTable(); String age = SP.getString(R.string.key_age, ""); int defaultHours = 24; @@ -116,7 +63,7 @@ public class SensitivityWeightedAveragePlugin implements PluginBase, Sensitivity return new AutosensResult(); } - AutosensData current = IobCobCalculatorPlugin.getAutosensData(toTime); // this is running inside lock already + AutosensData current = IobCobCalculatorPlugin.getPlugin().getAutosensData(toTime); // this is running inside lock already if (current == null) { if (Config.logAutosensData) log.debug("No autosens data available"); @@ -124,6 +71,13 @@ public class SensitivityWeightedAveragePlugin implements PluginBase, Sensitivity } + Profile profile = MainApp.getConfigBuilder().getProfile(); + if (profile == null) { + if (Config.logAutosensData) + log.debug("No profile available"); + return new AutosensResult(); + } + String pastSensitivity = ""; int index = 0; LongSparseArray data = new LongSparseArray<>(); @@ -181,8 +135,6 @@ public class SensitivityWeightedAveragePlugin implements PluginBase, Sensitivity return new AutosensResult(); } - Profile profile = MainApp.getConfigBuilder().getProfile(); - double sens = profile.getIsf(); String ratioLimit = ""; @@ -207,8 +159,8 @@ public class SensitivityWeightedAveragePlugin implements PluginBase, Sensitivity log.debug(sensResult); double rawRatio = ratio; - ratio = Math.max(ratio, SafeParse.stringToDouble(SP.getString("openapsama_autosens_min", "0.7"))); - ratio = Math.min(ratio, SafeParse.stringToDouble(SP.getString("openapsama_autosens_max", "1.2"))); + ratio = Math.max(ratio, SafeParse.stringToDouble(SP.getString(R.string.key_openapsama_autosens_min, "0.7"))); + ratio = Math.min(ratio, SafeParse.stringToDouble(SP.getString(R.string.key_openapsama_autosens_max, "1.2"))); if (ratio != rawRatio) { ratioLimit = "Ratio limited from " + rawRatio + " to " + ratio; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SmsCommunicator/SmsCommunicatorFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/SmsCommunicator/SmsCommunicatorFragment.java index b64049c098..d10786f8e5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SmsCommunicator/SmsCommunicatorFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/SmsCommunicator/SmsCommunicatorFragment.java @@ -10,7 +10,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; import com.squareup.otto.Subscribe; import org.slf4j.Logger; @@ -23,6 +22,7 @@ import info.nightscout.androidaps.R; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; import info.nightscout.androidaps.plugins.SmsCommunicator.events.EventSmsCommunicatorUpdateGui; import info.nightscout.utils.DateUtil; +import info.nightscout.utils.FabricPrivacy; /** * A simple {@link Fragment} subclass. @@ -47,7 +47,7 @@ public class SmsCommunicatorFragment extends SubscriberFragment { updateGUI(); return view; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } return null; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SmsCommunicator/SmsCommunicatorPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SmsCommunicator/SmsCommunicatorPlugin.java index b7b9d879b1..a0192a528c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SmsCommunicator/SmsCommunicatorPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/SmsCommunicator/SmsCommunicatorPlugin.java @@ -5,7 +5,6 @@ import android.content.pm.ResolveInfo; import android.telephony.SmsManager; import android.telephony.SmsMessage; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import com.squareup.otto.Subscribe; @@ -30,17 +29,21 @@ import info.nightscout.androidaps.db.DatabaseHelper; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.events.EventRefreshOverview; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Loop.LoopPlugin; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; -import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPlugin; -import info.nightscout.androidaps.plugins.PumpDanaRKorean.DanaRKoreanPlugin; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.SmsCommunicator.events.EventNewSMS; import info.nightscout.androidaps.plugins.SmsCommunicator.events.EventSmsCommunicatorUpdateGui; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.androidaps.queue.Callback; import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.NSUpload; import info.nightscout.utils.SP; import info.nightscout.utils.SafeParse; @@ -49,7 +52,7 @@ import info.nightscout.utils.XdripCalibrations; /** * Created by mike on 05.08.2016. */ -public class SmsCommunicatorPlugin implements PluginBase { +public class SmsCommunicatorPlugin extends PluginBase { private static Logger log = LoggerFactory.getLogger(SmsCommunicatorPlugin.class); private static SmsCommunicatorPlugin smsCommunicatorPlugin; @@ -62,11 +65,6 @@ public class SmsCommunicatorPlugin implements PluginBase { return smsCommunicatorPlugin; } - private boolean fragmentEnabled = false; - private boolean fragmentVisible = false; - - private final long CONFIRM_TIMEOUT = 5 * 60 * 1000L; - private List allowedNumbers = new ArrayList<>(); class Sms { @@ -120,74 +118,25 @@ public class SmsCommunicatorPlugin implements PluginBase { ArrayList messages = new ArrayList<>(); private SmsCommunicatorPlugin() { - MainApp.bus().register(this); + super(new PluginDescription() + .mainType(PluginType.GENERAL) + .fragmentClass(SmsCommunicatorFragment.class.getName()) + .pluginName(R.string.smscommunicator) + .shortName(R.string.smscommunicator_shortname) + .preferencesId(R.xml.pref_smscommunicator) + ); processSettings(null); } @Override - public String getFragmentClass() { - return SmsCommunicatorFragment.class.getName(); + protected void onStart() { + MainApp.bus().register(this); + super.onStart(); } @Override - public int getType() { - return PluginBase.GENERAL; - } - - @Override - public String getName() { - return MainApp.sResources.getString(R.string.smscommunicator); - } - - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.smscommunicator_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == GENERAL && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == GENERAL && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == GENERAL) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == GENERAL) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return R.xml.pref_smscommunicator; + protected void onStop() { + MainApp.bus().unregister(this); } @Subscribe @@ -217,15 +166,17 @@ public class SmsCommunicatorPlugin implements PluginBase { public void onStatusEvent(final EventNewSMS ev) { Object[] pdus = (Object[]) ev.bundle.get("pdus"); - // For every SMS message received - for (Object pdu : pdus) { - SmsMessage message = SmsMessage.createFromPdu((byte[]) pdu); - processSms(new Sms(message)); + if (pdus != null) { + // For every SMS message received + for (Object pdu : pdus) { + SmsMessage message = SmsMessage.createFromPdu((byte[]) pdu); + processSms(new Sms(message)); + } } } private void processSms(final Sms receivedSms) { - if (!isEnabled(PluginBase.GENERAL)) { + if (!isEnabled(PluginType.GENERAL)) { log.debug("Ignoring SMS. Plugin disabled."); return; } @@ -240,10 +191,10 @@ public class SmsCommunicatorPlugin implements PluginBase { log.debug(receivedSms.toString()); String[] splited = receivedSms.text.split("\\s+"); - Double amount = 0d; - Double tempBasal = 0d; + Double amount; + Double tempBasal; int duration = 0; - String passCode = ""; + String passCode; boolean remoteCommandsAllowed = SP.getBoolean(R.string.key_smscommunicator_remotecommandsallowed, false); if (splited.length > 0) { @@ -265,10 +216,10 @@ public class SmsCommunicatorPlugin implements PluginBase { if (glucoseStatus != null) reply += MainApp.sResources.getString(R.string.sms_delta) + " " + Profile.toUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units) + " " + units + ", "; - MainApp.getConfigBuilder().updateTotalIOBTreatments(); - IobTotal bolusIob = MainApp.getConfigBuilder().getLastCalculationTreatments().round(); - MainApp.getConfigBuilder().updateTotalIOBTempBasals(); - IobTotal basalIob = MainApp.getConfigBuilder().getLastCalculationTempBasals().round(); + TreatmentsPlugin.getPlugin().updateTotalIOBTreatments(); + IobTotal bolusIob = TreatmentsPlugin.getPlugin().getLastCalculationTreatments().round(); + TreatmentsPlugin.getPlugin().updateTotalIOBTempBasals(); + IobTotal basalIob = TreatmentsPlugin.getPlugin().getLastCalculationTempBasals().round(); reply += MainApp.sResources.getString(R.string.sms_iob) + " " + DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob) + "U (" + MainApp.sResources.getString(R.string.sms_bolus) + " " + DecimalFormatter.to2Decimal(bolusIob.iob) + "U " @@ -276,7 +227,7 @@ public class SmsCommunicatorPlugin implements PluginBase { sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); receivedSms.processed = true; - Answers.getInstance().logCustom(new CustomEvent("SMS_Bg")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("SMS_Bg")); break; case "LOOP": if (splited.length > 1) @@ -284,8 +235,8 @@ public class SmsCommunicatorPlugin implements PluginBase { case "DISABLE": case "STOP": LoopPlugin loopPlugin = MainApp.getSpecificPlugin(LoopPlugin.class); - if (loopPlugin != null && loopPlugin.isEnabled(PluginBase.LOOP)) { - loopPlugin.setFragmentEnabled(PluginBase.LOOP, false); + if (loopPlugin != null && loopPlugin.isEnabled(PluginType.LOOP)) { + loopPlugin.setPluginEnabled(PluginType.LOOP, false); ConfigBuilderPlugin.getCommandQueue().cancelTempBasal(true, new Callback() { @Override public void run() { @@ -297,24 +248,24 @@ public class SmsCommunicatorPlugin implements PluginBase { }); } receivedSms.processed = true; - Answers.getInstance().logCustom(new CustomEvent("SMS_Loop_Stop")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("SMS_Loop_Stop")); break; case "ENABLE": case "START": loopPlugin = MainApp.getSpecificPlugin(LoopPlugin.class); - if (loopPlugin != null && !loopPlugin.isEnabled(PluginBase.LOOP)) { - loopPlugin.setFragmentEnabled(PluginBase.LOOP, true); + if (loopPlugin != null && !loopPlugin.isEnabled(PluginType.LOOP)) { + loopPlugin.setPluginEnabled(PluginType.LOOP, true); reply = MainApp.sResources.getString(R.string.smscommunicator_loophasbeenenabled); sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); MainApp.bus().post(new EventRefreshOverview("SMS_LOOP_START")); } receivedSms.processed = true; - Answers.getInstance().logCustom(new CustomEvent("SMS_Loop_Start")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("SMS_Loop_Start")); break; case "STATUS": loopPlugin = MainApp.getSpecificPlugin(LoopPlugin.class); if (loopPlugin != null) { - if (loopPlugin.isEnabled(PluginBase.LOOP)) { + if (loopPlugin.isEnabled(PluginType.LOOP)) { if (loopPlugin.isSuspended()) reply = String.format(MainApp.sResources.getString(R.string.loopsuspendedfor), loopPlugin.minutesToEndOfSuspend()); else @@ -325,16 +276,15 @@ public class SmsCommunicatorPlugin implements PluginBase { sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); } receivedSms.processed = true; - Answers.getInstance().logCustom(new CustomEvent("SMS_Loop_Status")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("SMS_Loop_Status")); break; case "RESUME": - final LoopPlugin activeloop = ConfigBuilderPlugin.getActiveLoop(); - activeloop.suspendTo(0); + LoopPlugin.getPlugin().suspendTo(0); MainApp.bus().post(new EventRefreshOverview("SMS_LOOP_RESUME")); NSUpload.uploadOpenAPSOffline(0); reply = MainApp.sResources.getString(R.string.smscommunicator_loopresumed); sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, reply, new Date())); - Answers.getInstance().logCustom(new CustomEvent("SMS_Loop_Resume")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("SMS_Loop_Resume")); break; case "SUSPEND": if (splited.length >= 3) @@ -351,7 +301,7 @@ public class SmsCommunicatorPlugin implements PluginBase { resetWaitingMessages(); sendSMS(suspendWaitingForConfirmation = new Sms(receivedSms.phoneNumber, reply, new Date(), passCode)); suspendWaitingForConfirmation.duration = duration; - Answers.getInstance().logCustom(new CustomEvent("SMS_Loop_Suspend")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("SMS_Loop_Suspend")); } else { reply = MainApp.sResources.getString(R.string.smscommunicator_remotecommandnotallowed); sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); @@ -364,13 +314,13 @@ public class SmsCommunicatorPlugin implements PluginBase { switch (splited[1].toUpperCase()) { case "REFRESH": Intent restartNSClient = new Intent(Intents.ACTION_RESTART); - MainApp.getDbHelper().resetTreatments(); + TreatmentsPlugin.getPlugin().getService().resetTreatments(); MainApp.instance().getApplicationContext().sendBroadcast(restartNSClient); List q = MainApp.instance().getApplicationContext().getPackageManager().queryBroadcastReceivers(restartNSClient, 0); reply = "TERATMENTS REFRESH " + q.size() + " receivers"; sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); receivedSms.processed = true; - Answers.getInstance().logCustom(new CustomEvent("SMS_Treatments_Refresh")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("SMS_Treatments_Refresh")); break; } break; @@ -384,23 +334,29 @@ public class SmsCommunicatorPlugin implements PluginBase { reply = "NSCLIENT RESTART " + q.size() + " receivers"; sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); receivedSms.processed = true; - Answers.getInstance().logCustom(new CustomEvent("SMS_Nsclient_Restart")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("SMS_Nsclient_Restart")); break; } break; + case "PUMP": case "DANAR": - DanaRPlugin danaRPlugin = MainApp.getSpecificPlugin(DanaRPlugin.class); - if (danaRPlugin != null && danaRPlugin.isEnabled(PluginBase.PUMP)) { - reply = danaRPlugin.shortStatus(true); - sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); - } - DanaRKoreanPlugin danaRKoreanPlugin = MainApp.getSpecificPlugin(DanaRKoreanPlugin.class); - if (danaRKoreanPlugin != null && danaRKoreanPlugin.isEnabled(PluginBase.PUMP)) { - reply = danaRKoreanPlugin.shortStatus(true); - sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); - } + ConfigBuilderPlugin.getCommandQueue().readStatus("SMS", new Callback() { + @Override + public void run() { + PumpInterface pump = MainApp.getConfigBuilder().getActivePump(); + if (result.success) { + if (pump != null) { + String reply = pump.shortStatus(true); + sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, reply, new Date())); + } + } else { + String reply = MainApp.sResources.getString(R.string.readstatusfailed); + sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); + } + } + }); receivedSms.processed = true; - Answers.getInstance().logCustom(new CustomEvent("SMS_Danar")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("SMS_Pump")); break; case "BASAL": if (splited.length > 1) { @@ -411,25 +367,31 @@ public class SmsCommunicatorPlugin implements PluginBase { receivedSms.processed = true; resetWaitingMessages(); sendSMS(cancelTempBasalWaitingForConfirmation = new Sms(receivedSms.phoneNumber, reply, new Date(), passCode)); - Answers.getInstance().logCustom(new CustomEvent("SMS_Basal")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("SMS_Basal")); } else { reply = MainApp.sResources.getString(R.string.smscommunicator_remotebasalnotallowed); sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); } } else { tempBasal = SafeParse.stringToDouble(splited[1]); - tempBasal = MainApp.getConfigBuilder().applyBasalConstraints(tempBasal); - if (remoteCommandsAllowed) { - passCode = generatePasscode(); - reply = String.format(MainApp.sResources.getString(R.string.smscommunicator_basalreplywithcode), tempBasal, passCode); - receivedSms.processed = true; - resetWaitingMessages(); - sendSMS(tempBasalWaitingForConfirmation = new Sms(receivedSms.phoneNumber, reply, new Date(), passCode)); - tempBasalWaitingForConfirmation.tempBasal = tempBasal; - Answers.getInstance().logCustom(new CustomEvent("SMS_Basal")); - } else { - reply = MainApp.sResources.getString(R.string.smscommunicator_remotebasalnotallowed); + Profile profile = MainApp.getConfigBuilder().getProfile(); + if (profile == null) { + reply = MainApp.sResources.getString(R.string.noprofile); sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); + } else { + tempBasal = MainApp.getConstraintChecker().applyBasalConstraints(new Constraint<>(tempBasal), profile).value(); + if (remoteCommandsAllowed) { + passCode = generatePasscode(); + reply = String.format(MainApp.sResources.getString(R.string.smscommunicator_basalreplywithcode), tempBasal, passCode); + receivedSms.processed = true; + resetWaitingMessages(); + sendSMS(tempBasalWaitingForConfirmation = new Sms(receivedSms.phoneNumber, reply, new Date(), passCode)); + tempBasalWaitingForConfirmation.tempBasal = tempBasal; + FabricPrivacy.getInstance().logCustom(new CustomEvent("SMS_Basal")); + } else { + reply = MainApp.sResources.getString(R.string.smscommunicator_remotebasalnotallowed); + sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); + } } } } @@ -443,7 +405,7 @@ public class SmsCommunicatorPlugin implements PluginBase { sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); } else if (splited.length > 1) { amount = SafeParse.stringToDouble(splited[1]); - amount = MainApp.getConfigBuilder().applyBolusConstraints(amount); + amount = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(amount)).value(); if (amount > 0d && remoteCommandsAllowed) { passCode = generatePasscode(); reply = String.format(MainApp.sResources.getString(R.string.smscommunicator_bolusreplywithcode), amount, passCode); @@ -451,7 +413,7 @@ public class SmsCommunicatorPlugin implements PluginBase { resetWaitingMessages(); sendSMS(bolusWaitingForConfirmation = new Sms(receivedSms.phoneNumber, reply, new Date(), passCode)); bolusWaitingForConfirmation.bolusRequested = amount; - Answers.getInstance().logCustom(new CustomEvent("SMS_Bolus")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("SMS_Bolus")); } else { reply = MainApp.sResources.getString(R.string.smscommunicator_remotebolusnotallowed); sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); @@ -468,7 +430,7 @@ public class SmsCommunicatorPlugin implements PluginBase { resetWaitingMessages(); sendSMS(calibrationWaitingForConfirmation = new Sms(receivedSms.phoneNumber, reply, new Date(), passCode)); calibrationWaitingForConfirmation.calibrationRequested = amount; - Answers.getInstance().logCustom(new CustomEvent("SMS_Cal")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("SMS_Cal")); } else { reply = MainApp.sResources.getString(R.string.smscommunicator_remotecalibrationnotallowed); sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); @@ -477,7 +439,7 @@ public class SmsCommunicatorPlugin implements PluginBase { break; default: // expect passCode here if (bolusWaitingForConfirmation != null && !bolusWaitingForConfirmation.processed && - bolusWaitingForConfirmation.confirmCode.equals(splited[0]) && System.currentTimeMillis() - bolusWaitingForConfirmation.date.getTime() < CONFIRM_TIMEOUT) { + bolusWaitingForConfirmation.confirmCode.equals(splited[0]) && System.currentTimeMillis() - bolusWaitingForConfirmation.date.getTime() < Constants.SMS_CONFIRM_TIMEOUT) { bolusWaitingForConfirmation.processed = true; DetailedBolusInfo detailedBolusInfo = new DetailedBolusInfo(); detailedBolusInfo.insulin = bolusWaitingForConfirmation.bolusRequested; @@ -485,40 +447,42 @@ public class SmsCommunicatorPlugin implements PluginBase { ConfigBuilderPlugin.getCommandQueue().bolus(detailedBolusInfo, new Callback() { @Override public void run() { - DanaRPlugin danaRPlugin = MainApp.getSpecificPlugin(DanaRPlugin.class); + PumpInterface pump = MainApp.getConfigBuilder().getActivePump(); if (result.success) { String reply = String.format(MainApp.sResources.getString(R.string.smscommunicator_bolusdelivered), result.bolusDelivered); - if (danaRPlugin != null) - reply += "\n" + danaRPlugin.shortStatus(true); + if (pump != null) + reply += "\n" + pump.shortStatus(true); lastRemoteBolusTime = new Date(); sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, reply, new Date())); } else { String reply = MainApp.sResources.getString(R.string.smscommunicator_bolusfailed); - if (danaRPlugin != null) - reply += "\n" + danaRPlugin.shortStatus(true); + if (pump != null) + reply += "\n" + pump.shortStatus(true); sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); } } }); } else if (tempBasalWaitingForConfirmation != null && !tempBasalWaitingForConfirmation.processed && - tempBasalWaitingForConfirmation.confirmCode.equals(splited[0]) && System.currentTimeMillis() - tempBasalWaitingForConfirmation.date.getTime() < CONFIRM_TIMEOUT) { + tempBasalWaitingForConfirmation.confirmCode.equals(splited[0]) && System.currentTimeMillis() - tempBasalWaitingForConfirmation.date.getTime() < Constants.SMS_CONFIRM_TIMEOUT) { tempBasalWaitingForConfirmation.processed = true; - ConfigBuilderPlugin.getCommandQueue().tempBasalAbsolute(tempBasalWaitingForConfirmation.tempBasal, 30, true, new Callback() { - @Override - public void run() { - if (result.success) { - String reply = String.format(MainApp.sResources.getString(R.string.smscommunicator_tempbasalset), result.absolute, result.duration); - reply += "\n" + ConfigBuilderPlugin.getActivePump().shortStatus(true); - sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, reply, new Date())); - } else { - String reply = MainApp.sResources.getString(R.string.smscommunicator_tempbasalfailed); - reply += "\n" + ConfigBuilderPlugin.getActivePump().shortStatus(true); - sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); + Profile profile = MainApp.getConfigBuilder().getProfile(); + if (profile != null) + ConfigBuilderPlugin.getCommandQueue().tempBasalAbsolute(tempBasalWaitingForConfirmation.tempBasal, 30, true, profile, new Callback() { + @Override + public void run() { + if (result.success) { + String reply = String.format(MainApp.sResources.getString(R.string.smscommunicator_tempbasalset), result.absolute, result.duration); + reply += "\n" + ConfigBuilderPlugin.getActivePump().shortStatus(true); + sendSMSToAllNumbers(new Sms(receivedSms.phoneNumber, reply, new Date())); + } else { + String reply = MainApp.sResources.getString(R.string.smscommunicator_tempbasalfailed); + reply += "\n" + ConfigBuilderPlugin.getActivePump().shortStatus(true); + sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); + } } - } - }); + }); } else if (cancelTempBasalWaitingForConfirmation != null && !cancelTempBasalWaitingForConfirmation.processed && - cancelTempBasalWaitingForConfirmation.confirmCode.equals(splited[0]) && System.currentTimeMillis() - cancelTempBasalWaitingForConfirmation.date.getTime() < CONFIRM_TIMEOUT) { + cancelTempBasalWaitingForConfirmation.confirmCode.equals(splited[0]) && System.currentTimeMillis() - cancelTempBasalWaitingForConfirmation.date.getTime() < Constants.SMS_CONFIRM_TIMEOUT) { cancelTempBasalWaitingForConfirmation.processed = true; ConfigBuilderPlugin.getCommandQueue().cancelTempBasal(true, new Callback() { @Override @@ -535,7 +499,7 @@ public class SmsCommunicatorPlugin implements PluginBase { } }); } else if (calibrationWaitingForConfirmation != null && !calibrationWaitingForConfirmation.processed && - calibrationWaitingForConfirmation.confirmCode.equals(splited[0]) && System.currentTimeMillis() - calibrationWaitingForConfirmation.date.getTime() < CONFIRM_TIMEOUT) { + calibrationWaitingForConfirmation.confirmCode.equals(splited[0]) && System.currentTimeMillis() - calibrationWaitingForConfirmation.date.getTime() < Constants.SMS_CONFIRM_TIMEOUT) { calibrationWaitingForConfirmation.processed = true; boolean result = XdripCalibrations.sendIntent(calibrationWaitingForConfirmation.calibrationRequested); if (result) { @@ -546,14 +510,13 @@ public class SmsCommunicatorPlugin implements PluginBase { sendSMS(new Sms(receivedSms.phoneNumber, reply, new Date())); } } else if (suspendWaitingForConfirmation != null && !suspendWaitingForConfirmation.processed && - suspendWaitingForConfirmation.confirmCode.equals(splited[0]) && System.currentTimeMillis() - suspendWaitingForConfirmation.date.getTime() < CONFIRM_TIMEOUT) { + suspendWaitingForConfirmation.confirmCode.equals(splited[0]) && System.currentTimeMillis() - suspendWaitingForConfirmation.date.getTime() < Constants.SMS_CONFIRM_TIMEOUT) { suspendWaitingForConfirmation.processed = true; ConfigBuilderPlugin.getCommandQueue().cancelTempBasal(true, new Callback() { @Override public void run() { if (result.success) { - final LoopPlugin activeloop = ConfigBuilderPlugin.getActiveLoop(); - activeloop.suspendTo(System.currentTimeMillis() + suspendWaitingForConfirmation.duration * 60L * 1000); + LoopPlugin.getPlugin().suspendTo(System.currentTimeMillis() + suspendWaitingForConfirmation.duration * 60L * 1000); NSUpload.uploadOpenAPSOffline(suspendWaitingForConfirmation.duration * 60); MainApp.bus().post(new EventRefreshOverview("SMS_LOOP_SUSPENDED")); String reply = MainApp.sResources.getString(R.string.smscommunicator_loopsuspended) + " " + diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SourceDexcomG5/BGSourceFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Source/BGSourceFragment.java similarity index 94% rename from app/src/main/java/info/nightscout/androidaps/plugins/SourceDexcomG5/BGSourceFragment.java rename to app/src/main/java/info/nightscout/androidaps/plugins/Source/BGSourceFragment.java index 0b001b8b80..7e959b78d4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SourceDexcomG5/BGSourceFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Source/BGSourceFragment.java @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.SourceDexcomG5; +package info.nightscout.androidaps.plugins.Source; import android.app.Activity; import android.content.DialogInterface; @@ -12,7 +12,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; import com.squareup.otto.Subscribe; import org.slf4j.Logger; @@ -26,9 +25,8 @@ import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.events.EventNewBG; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.NSClientInternal.UploadQueue; import info.nightscout.utils.DateUtil; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.NSUpload; /** @@ -59,11 +57,11 @@ public class BGSourceFragment extends SubscriberFragment { RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainApp.getDbHelper().getAllBgreadingsDataFromTime(now - MILLS_TO_THE_PAST, false)); recyclerView.setAdapter(adapter); - profile = ConfigBuilderPlugin.getActiveProfileInterface().getProfile().getDefaultProfile(); + profile = MainApp.getConfigBuilder().getActiveProfileInterface().getProfile().getDefaultProfile(); return view; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } return null; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Source/SourceDexcomG5Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Source/SourceDexcomG5Plugin.java new file mode 100644 index 0000000000..d526a12971 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Source/SourceDexcomG5Plugin.java @@ -0,0 +1,39 @@ +package info.nightscout.androidaps.plugins.Source; + +import info.nightscout.androidaps.Config; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.interfaces.BgSourceInterface; +import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; + +/** + * Created by mike on 28.11.2017. + */ + +public class SourceDexcomG5Plugin extends PluginBase implements BgSourceInterface { + + private static SourceDexcomG5Plugin plugin = null; + + public static SourceDexcomG5Plugin getPlugin() { + if (plugin == null) + plugin = new SourceDexcomG5Plugin(); + return plugin; + } + + private SourceDexcomG5Plugin() { + super(new PluginDescription() + .mainType(PluginType.BGSOURCE) + .fragmentClass(BGSourceFragment.class.getName()) + .pluginName(R.string.DexcomG5) + .shortName(R.string.dexcomG5_shortname) + .showInList(!Config.NSCLIENT) + .preferencesId(R.xml.pref_dexcomg5) + ); + } + + @Override + public boolean advancedFilteringSupported() { + return true; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Source/SourceGlimpPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Source/SourceGlimpPlugin.java new file mode 100644 index 0000000000..0846885df7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Source/SourceGlimpPlugin.java @@ -0,0 +1,34 @@ +package info.nightscout.androidaps.plugins.Source; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.interfaces.BgSourceInterface; +import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; + +/** + * Created by mike on 05.08.2016. + */ +public class SourceGlimpPlugin extends PluginBase implements BgSourceInterface { + + private static SourceGlimpPlugin plugin = null; + + public static SourceGlimpPlugin getPlugin() { + if (plugin == null) + plugin = new SourceGlimpPlugin(); + return plugin; + } + + private SourceGlimpPlugin() { + super(new PluginDescription() + .mainType(PluginType.BGSOURCE) + .fragmentClass(BGSourceFragment.class.getName()) + .pluginName(R.string.Glimp) + ); + } + + @Override + public boolean advancedFilteringSupported() { + return false; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Source/SourceMM640gPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Source/SourceMM640gPlugin.java new file mode 100644 index 0000000000..8df63df1e6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Source/SourceMM640gPlugin.java @@ -0,0 +1,33 @@ +package info.nightscout.androidaps.plugins.Source; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.interfaces.BgSourceInterface; +import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; + +/** + * Created by mike on 05.08.2016. + */ +public class SourceMM640gPlugin extends PluginBase implements BgSourceInterface { + private static SourceMM640gPlugin plugin = null; + + public static SourceMM640gPlugin getPlugin() { + if (plugin == null) + plugin = new SourceMM640gPlugin(); + return plugin; + } + + private SourceMM640gPlugin() { + super(new PluginDescription() + .mainType(PluginType.BGSOURCE) + .fragmentClass(BGSourceFragment.class.getName()) + .pluginName(R.string.MM640g) + ); + } + + @Override + public boolean advancedFilteringSupported() { + return false; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Source/SourceNSClientPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Source/SourceNSClientPlugin.java new file mode 100644 index 0000000000..3ba85e97a6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Source/SourceNSClientPlugin.java @@ -0,0 +1,37 @@ +package info.nightscout.androidaps.plugins.Source; + +import info.nightscout.androidaps.Config; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.interfaces.BgSourceInterface; +import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; + +/** + * Created by mike on 05.08.2016. + */ +public class SourceNSClientPlugin extends PluginBase implements BgSourceInterface { + + private static SourceNSClientPlugin plugin = null; + + public static SourceNSClientPlugin getPlugin() { + if (plugin == null) + plugin = new SourceNSClientPlugin(); + return plugin; + } + + private SourceNSClientPlugin() { + super(new PluginDescription() + .mainType(PluginType.BGSOURCE) + .fragmentClass(BGSourceFragment.class.getName()) + .pluginName(R.string.nsclientbg) + .showInList(!Config.NSCLIENT) + .alwaysEnabled(Config.NSCLIENT) + ); + } + + @Override + public boolean advancedFilteringSupported() { + return true; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Source/SourceXdripPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Source/SourceXdripPlugin.java new file mode 100644 index 0000000000..bd3d96162e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Source/SourceXdripPlugin.java @@ -0,0 +1,40 @@ +package info.nightscout.androidaps.plugins.Source; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.interfaces.BgSourceInterface; +import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; + +/** + * Created by mike on 05.08.2016. + */ +public class SourceXdripPlugin extends PluginBase implements BgSourceInterface { + + private static SourceXdripPlugin plugin = null; + + boolean advancedFiltering; + + public static SourceXdripPlugin getPlugin() { + if (plugin == null) + plugin = new SourceXdripPlugin(); + return plugin; + } + + private SourceXdripPlugin() { + super(new PluginDescription() + .mainType(PluginType.BGSOURCE) + .fragmentClass(BGSourceFragment.class.getName()) + .pluginName(R.string.xdrip) + ); + } + + @Override + public boolean advancedFilteringSupported() { + return advancedFiltering; + } + + public void setSource(String source) { + this.advancedFiltering = source.contains("G5 Native"); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SourceDexcomG5/SourceDexcomG5Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SourceDexcomG5/SourceDexcomG5Plugin.java deleted file mode 100644 index 04f5700dae..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SourceDexcomG5/SourceDexcomG5Plugin.java +++ /dev/null @@ -1,85 +0,0 @@ -package info.nightscout.androidaps.plugins.SourceDexcomG5; - -import info.nightscout.androidaps.Config; -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.interfaces.BgSourceInterface; -import info.nightscout.androidaps.interfaces.PluginBase; - -/** - * Created by mike on 28.11.2017. - */ - -public class SourceDexcomG5Plugin implements PluginBase, BgSourceInterface { - private boolean fragmentEnabled = false; - private boolean fragmentVisible = false; - - private static SourceDexcomG5Plugin plugin = null; - - public static SourceDexcomG5Plugin getPlugin() { - if (plugin == null) - plugin = new SourceDexcomG5Plugin(); - return plugin; - } - - @Override - public String getFragmentClass() { - return BGSourceFragment.class.getName(); - } - - @Override - public int getType() { - return PluginBase.BGSOURCE; - } - - @Override - public String getName() { - return MainApp.instance().getString(R.string.DexcomG5); - } - - @Override - public String getNameShort() { - // use long name as fallback (no tabs) - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return Config.G5UPLOADER || type == BGSOURCE && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return Config.G5UPLOADER || type == BGSOURCE && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return !Config.G5UPLOADER; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == BGSOURCE) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == BGSOURCE) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return R.xml.pref_dexcomg5; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SourceGlimp/SourceGlimpPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SourceGlimp/SourceGlimpPlugin.java deleted file mode 100644 index 51fd755b07..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SourceGlimp/SourceGlimpPlugin.java +++ /dev/null @@ -1,86 +0,0 @@ -package info.nightscout.androidaps.plugins.SourceGlimp; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.interfaces.BgSourceInterface; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.plugins.SourceDexcomG5.BGSourceFragment; - -/** - * Created by mike on 05.08.2016. - */ -public class SourceGlimpPlugin implements PluginBase, BgSourceInterface { - private boolean fragmentEnabled = false; - private boolean fragmentVisible = false; - - private static SourceGlimpPlugin plugin = null; - - public static SourceGlimpPlugin getPlugin() { - if (plugin == null) - plugin = new SourceGlimpPlugin(); - return plugin; - } - - @Override - public String getFragmentClass() { - return BGSourceFragment.class.getName(); - } - - @Override - public int getType() { - return PluginBase.BGSOURCE; - } - - @Override - public String getName() { - return MainApp.instance().getString(R.string.Glimp); - } - - @Override - public String getNameShort() { - // use long name as fallback (no tabs) - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == BGSOURCE && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == BGSOURCE && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == BGSOURCE) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == BGSOURCE) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return -1; - } - - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SourceMM640g/SourceMM640gPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SourceMM640g/SourceMM640gPlugin.java deleted file mode 100644 index e530dd563b..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SourceMM640g/SourceMM640gPlugin.java +++ /dev/null @@ -1,86 +0,0 @@ -package info.nightscout.androidaps.plugins.SourceMM640g; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.interfaces.BgSourceInterface; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.plugins.SourceDexcomG5.BGSourceFragment; - -/** - * Created by mike on 05.08.2016. - */ -public class SourceMM640gPlugin implements PluginBase, BgSourceInterface { - private boolean fragmentEnabled = false; - private boolean fragmentVisible = false; - - private static SourceMM640gPlugin plugin = null; - - public static SourceMM640gPlugin getPlugin() { - if (plugin == null) - plugin = new SourceMM640gPlugin(); - return plugin; - } - - @Override - public String getFragmentClass() { - return BGSourceFragment.class.getName(); - } - - @Override - public int getType() { - return PluginBase.BGSOURCE; - } - - @Override - public String getName() { - return MainApp.instance().getString(R.string.MM640g); - } - - @Override - public String getNameShort() { - // use long name as fallback (no tabs) - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == BGSOURCE && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == BGSOURCE && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == BGSOURCE) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == BGSOURCE) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return -1; - } - - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SourceNSClient/SourceNSClientPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SourceNSClient/SourceNSClientPlugin.java deleted file mode 100644 index 1928448f0e..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SourceNSClient/SourceNSClientPlugin.java +++ /dev/null @@ -1,88 +0,0 @@ -package info.nightscout.androidaps.plugins.SourceNSClient; - -import info.nightscout.androidaps.Config; -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.interfaces.BgSourceInterface; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.plugins.SourceDexcomG5.BGSourceFragment; - -/** - * Created by mike on 05.08.2016. - */ -public class SourceNSClientPlugin implements PluginBase, BgSourceInterface { - private boolean fragmentEnabled = true; - private boolean fragmentVisible = false; - - private static SourceNSClientPlugin plugin = null; - - public static SourceNSClientPlugin getPlugin() { - if (plugin == null) - plugin = new SourceNSClientPlugin(); - return plugin; - } - - @Override - public String getFragmentClass() { - return BGSourceFragment.class.getName(); - } - - @Override - public int getType() { - return PluginBase.BGSOURCE; - } - - @Override - public String getName() { - return MainApp.instance().getString(R.string.nsclientbg); - } - - @Override - public String getNameShort() { - // use long name as fallback (not visible in tabs) - return getName(); - } - - - @Override - public boolean isEnabled(int type) { - return Config.NSCLIENT || type == BGSOURCE && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == BGSOURCE && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return !Config.NSCLIENT; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == BGSOURCE) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == BGSOURCE) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return -1; - } - - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SourceXdrip/SourceXdripPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SourceXdrip/SourceXdripPlugin.java deleted file mode 100644 index 7f73d457e7..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SourceXdrip/SourceXdripPlugin.java +++ /dev/null @@ -1,87 +0,0 @@ -package info.nightscout.androidaps.plugins.SourceXdrip; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.interfaces.BgSourceInterface; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.plugins.SourceDexcomG5.BGSourceFragment; - -/** - * Created by mike on 05.08.2016. - */ -public class SourceXdripPlugin implements PluginBase, BgSourceInterface { - - private boolean fragmentEnabled = false; - private boolean fragmentVisible = false; - - private static SourceXdripPlugin plugin = null; - - public static SourceXdripPlugin getPlugin() { - if (plugin == null) - plugin = new SourceXdripPlugin(); - return plugin; - } - - @Override - public String getFragmentClass() { - return BGSourceFragment.class.getName(); - } - - @Override - public int getType() { - return PluginBase.BGSOURCE; - } - - @Override - public String getName() { - return MainApp.instance().getString(R.string.xdrip); - } - - @Override - public String getNameShort() { - // use long name as fallback (no tabs) - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == BGSOURCE && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == BGSOURCE && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == BGSOURCE) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == BGSOURCE) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return -1; - } - - -} diff --git a/app/src/main/java/info/nightscout/androidaps/db/Treatment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/Treatment.java similarity index 66% rename from app/src/main/java/info/nightscout/androidaps/db/Treatment.java rename to app/src/main/java/info/nightscout/androidaps/plugins/Treatments/Treatment.java index 9de76e6bf8..31d44a3e30 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/Treatment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/Treatment.java @@ -1,10 +1,12 @@ -package info.nightscout.androidaps.db; +package info.nightscout.androidaps.plugins.Treatments; import android.graphics.Color; import com.j256.ormlite.field.DatabaseField; import com.j256.ormlite.table.DatabaseTable; +import org.json.JSONException; +import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,18 +14,25 @@ import java.util.Objects; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Iob; +import info.nightscout.androidaps.db.DatabaseHelper; +import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.interfaces.InsulinInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.Overview.OverviewPlugin; import info.nightscout.androidaps.plugins.Overview.graphExtensions.DataPointWithLabelInterface; import info.nightscout.androidaps.plugins.Overview.graphExtensions.PointsWithLabelGraphSeries; import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.JsonHelper; -@DatabaseTable(tableName = DatabaseHelper.DATABASE_TREATMENTS) +@DatabaseTable(tableName = Treatment.TABLE_TREATMENTS) public class Treatment implements DataPointWithLabelInterface { private static Logger log = LoggerFactory.getLogger(Treatment.class); + public static final String TABLE_TREATMENTS = "Treatments"; + @DatabaseField(id = true) public long date; @@ -55,6 +64,32 @@ public class Treatment implements DataPointWithLabelInterface { public Treatment() { } + public static Treatment createFromJson(JSONObject json) throws JSONException { + Treatment treatment = new Treatment(); + treatment.source = Source.NIGHTSCOUT; + treatment.date = DateUtil.roundDateToSec(JsonHelper.safeGetLong(json, "mills")); + if (treatment.date == 0L) + return null; + treatment.carbs = JsonHelper.safeGetDouble(json,"carbs"); + treatment.insulin = JsonHelper.safeGetDouble(json,"insulin"); + treatment.pumpId = JsonHelper.safeGetLong(json, "pumpId"); + treatment._id = json.getString("_id"); + treatment.isSMB = JsonHelper.safeGetBoolean(json,"isSMB"); + if (json.has("eventType")) { + treatment.mealBolus = !json.get("eventType").equals("Correction Bolus"); + double carbs = treatment.carbs; + if (json.has("boluscalc")) { + JSONObject boluscalc = json.getJSONObject("boluscalc"); + if (boluscalc.has("carbs")) { + carbs = Math.max(boluscalc.getDouble("carbs"), carbs); + } + } + if (carbs <= 0) + treatment.mealBolus = false; + } + return treatment; + } + public String toString() { return "Treatment{" + "date= " + date + @@ -70,9 +105,8 @@ public class Treatment implements DataPointWithLabelInterface { } public boolean isDataChanging(Treatment other) { - if (date != other.date) { + if (date != other.date) return true; - } if (insulin != other.insulin) return true; if (carbs != other.carbs) @@ -81,9 +115,8 @@ public class Treatment implements DataPointWithLabelInterface { } public boolean isEqual(Treatment other) { - if (date != other.date) { + if (date != other.date) return false; - } if (insulin != other.insulin) return false; if (carbs != other.carbs) @@ -120,13 +153,13 @@ public class Treatment implements DataPointWithLabelInterface { @Override public double getY() { - return yValue; + return isSMB ? OverviewPlugin.getPlugin().determineLowLine() : yValue; } @Override public String getLabel() { String label = ""; - if (insulin > 0) label += DecimalFormatter.to2Decimal(insulin) + "U"; + if (insulin > 0) label += DecimalFormatter.toPumpSupportedBolus(insulin) + "U"; if (carbs > 0) label += "~" + DecimalFormatter.to0Decimal(carbs) + "g"; return label; @@ -139,22 +172,32 @@ public class Treatment implements DataPointWithLabelInterface { @Override public PointsWithLabelGraphSeries.Shape getShape() { - return PointsWithLabelGraphSeries.Shape.BOLUS; + if (isSMB) + return PointsWithLabelGraphSeries.Shape.SMB; + else + return PointsWithLabelGraphSeries.Shape.BOLUS; } @Override public float getSize() { - return 10; + return 2; } @Override public int getColor() { - if (isValid) + if (isSMB) + return MainApp.sResources.getColor(R.color.tempbasal); + else if (isValid) return Color.CYAN; else return MainApp.instance().getResources().getColor(android.R.color.holo_red_light); } + @Override + public int getSecondColor() { + return 0; + } + @Override public void setY(double y) { yValue = y; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentService.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentService.java new file mode 100644 index 0000000000..2a5b798018 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentService.java @@ -0,0 +1,426 @@ +package info.nightscout.androidaps.plugins.Treatments; + +import android.content.Intent; +import android.os.IBinder; +import android.support.annotation.Nullable; + +import com.j256.ormlite.android.apptools.OpenHelperManager; +import com.j256.ormlite.android.apptools.OrmLiteBaseService; +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.DaoManager; +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 com.squareup.otto.Subscribe; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.db.DatabaseHelper; +import info.nightscout.androidaps.db.ICallback; +import info.nightscout.androidaps.db.Source; +import info.nightscout.androidaps.events.Event; +import info.nightscout.androidaps.events.EventNsTreatment; +import info.nightscout.androidaps.events.EventReloadTreatmentData; +import info.nightscout.androidaps.events.EventTreatmentChange; +import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventNewHistoryData; +import info.nightscout.utils.JsonHelper; + + +/** + * Created by mike on 24.09.2017. + */ + +public class TreatmentService extends OrmLiteBaseService { + private static Logger log = LoggerFactory.getLogger(TreatmentService.class); + + private static final ScheduledExecutorService treatmentEventWorker = Executors.newSingleThreadScheduledExecutor(); + private static ScheduledFuture scheduledTreatmentEventPost = null; + + public TreatmentService() { + onCreate(); + dbInitialize(); + MainApp.bus().register(this); + } + + /** + * This method is a simple re-implementation of the database create and up/downgrade functionality + * in SQLiteOpenHelper#getDatabaseLocked method. + *

+ * It is implemented to be able to late initialize separate plugins of the application. + */ + protected void dbInitialize() { + DatabaseHelper helper = OpenHelperManager.getHelper(this, DatabaseHelper.class); + int newVersion = helper.getNewVersion(); + int oldVersion = helper.getOldVersion(); + + if (oldVersion > newVersion) { + onDowngrade(this.getConnectionSource(), oldVersion, newVersion); + } else { + onUpgrade(this.getConnectionSource(), oldVersion, newVersion); + } + } + + public Dao getDao() { + try { + return DaoManager.createDao(this.getConnectionSource(), Treatment.class); + } catch (SQLException e) { + log.error("Cannot create Dao for Treatment.class"); + } + + return null; + } + + @Subscribe + @SuppressWarnings("unused") + public void handleNsEvent(EventNsTreatment event) { + int mode = event.getMode(); + JSONObject payload = event.getPayload(); + + if (mode == EventNsTreatment.ADD || mode == EventNsTreatment.UPDATE) { + this.createTreatmentFromJsonIfNotExists(payload); + } else { // EventNsTreatment.REMOVE + this.deleteNS(payload); + } + } + + @Override + public void onCreate() { + super.onCreate(); + try { + log.info("onCreate"); + TableUtils.createTableIfNotExists(this.getConnectionSource(), Treatment.class); + } catch (SQLException e) { + log.error("Can't create database", e); + throw new RuntimeException(e); + } + } + + public void onUpgrade(ConnectionSource connectionSource, int oldVersion, int newVersion) { + if (oldVersion == 7 && newVersion == 8) { + log.debug("Upgrading database from v7 to v8"); + try { + TableUtils.dropTable(connectionSource, Treatment.class, true); + TableUtils.createTableIfNotExists(connectionSource, Treatment.class); + } catch (SQLException e) { + log.error("Can't create database", e); + throw new RuntimeException(e); + } + } else { + log.info("onUpgrade"); +// this.resetFood(); + } + } + + public void onDowngrade(ConnectionSource connectionSource, int oldVersion, int newVersion) { + // this method is not supported right now + } + + public void resetTreatments() { + try { + TableUtils.dropTable(this.getConnectionSource(), Treatment.class, true); + TableUtils.createTableIfNotExists(this.getConnectionSource(), Treatment.class); + DatabaseHelper.updateEarliestDataChange(0); + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + scheduleTreatmentChange(null); + } + + + /** + * A place to centrally register events to be posted, if any data changed. + * This should be implemented in an abstract service-class. + *

+ * We do need to make sure, that ICallback is extended to be able to handle multiple + * events, or handle a list of events. + *

+ * on some methods the earliestDataChange event is handled separatly, in that it is checked if it is + * set to null by another event already (eg. scheduleExtendedBolusChange). + * + * @param event + * @param eventWorker + * @param callback + */ + private void scheduleEvent(final Event event, ScheduledExecutorService eventWorker, + final ICallback callback) { + + class PostRunnable implements Runnable { + public void run() { + log.debug("Firing EventFoodChange"); + MainApp.bus().post(event); + if (DatabaseHelper.earliestDataChange != null) + MainApp.bus().post(new EventNewHistoryData(DatabaseHelper.earliestDataChange)); + DatabaseHelper.earliestDataChange = null; + callback.setPost(null); + } + } + // prepare task for execution in 1 sec + // cancel waiting task to prevent sending multiple posts + if (callback.getPost() != null) + callback.getPost().cancel(false); + Runnable task = new PostRunnable(); + final int sec = 1; + callback.setPost(eventWorker.schedule(task, sec, TimeUnit.SECONDS)); + } + + /** + * Schedule a foodChange Event. + */ + public void scheduleTreatmentChange(@Nullable final Treatment treatment) { + this.scheduleEvent(new EventReloadTreatmentData(new EventTreatmentChange(treatment)), treatmentEventWorker, new ICallback() { + @Override + public void setPost(ScheduledFuture post) { + scheduledTreatmentEventPost = post; + } + + @Override + public ScheduledFuture getPost() { + return scheduledTreatmentEventPost; + } + }); + } + + public List getTreatmentData() { + try { + return this.getDao().queryForAll(); + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + + return new ArrayList<>(); + } + + /* + { + "_id": "551ee3ad368e06e80856e6a9", + "type": "food", + "category": "Zakladni", + "subcategory": "Napoje", + "name": "Mleko", + "portion": 250, + "carbs": 12, + "gi": 1, + "created_at": "2015-04-14T06:59:16.500Z", + "unit": "ml" + } + */ + public void createTreatmentFromJsonIfNotExists(JSONObject json) { + try { + Treatment treatment = Treatment.createFromJson(json); + if (treatment != null) + createOrUpdate(treatment); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + } + + public void createFoodFromJsonIfNotExists(JSONArray array) { + try { + for (int n = 0; n < array.length(); n++) { + JSONObject json = array.getJSONObject(n); + createTreatmentFromJsonIfNotExists(json); + } + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + } + + // return true if new record is created + public boolean createOrUpdate(Treatment treatment) { + try { + Treatment old; + treatment.date = DatabaseHelper.roundDateToSec(treatment.date); + + if (treatment.source == Source.PUMP) { + // check for changed from pump change in NS + QueryBuilder queryBuilder = getDao().queryBuilder(); + Where where = queryBuilder.where(); + where.eq("pumpId", treatment.pumpId); + PreparedQuery preparedQuery = queryBuilder.prepare(); + List trList = getDao().query(preparedQuery); + if (trList.size() > 0) { + // do nothing, pump history record cannot be changed + log.debug("TREATMENT: Pump record already found in database: " + treatment.toString()); + return false; + } + getDao().create(treatment); + log.debug("TREATMENT: New record from: " + Source.getString(treatment.source) + " " + treatment.toString()); + DatabaseHelper.updateEarliestDataChange(treatment.date); + scheduleTreatmentChange(treatment); + return true; + } + if (treatment.source == Source.NIGHTSCOUT) { + old = getDao().queryForId(treatment.date); + if (old != null) { + if (!old.isEqual(treatment)) { + boolean historyChange = old.isDataChanging(treatment); + long oldDate = old.date; + getDao().delete(old); // need to delete/create because date may change too + old.copyFrom(treatment); + getDao().create(old); + log.debug("TREATMENT: Updating record by date from: " + Source.getString(treatment.source) + " " + old.toString()); + if (historyChange) { + DatabaseHelper.updateEarliestDataChange(oldDate); + DatabaseHelper.updateEarliestDataChange(old.date); + } + scheduleTreatmentChange(treatment); + return true; + } + return false; + } + // find by NS _id + if (treatment._id != null) { + old = findByNSId(treatment._id); + if (old != null) { + if (!old.isEqual(treatment)) { + boolean historyChange = old.isDataChanging(treatment); + long oldDate = old.date; + getDao().delete(old); // need to delete/create because date may change too + old.copyFrom(treatment); + getDao().create(old); + log.debug("TREATMENT: Updating record by _id from: " + Source.getString(treatment.source) + " " + old.toString()); + if (historyChange) { + DatabaseHelper.updateEarliestDataChange(oldDate); + DatabaseHelper.updateEarliestDataChange(old.date); + } + scheduleTreatmentChange(treatment); + return true; + } + } + } + getDao().create(treatment); + log.debug("TREATMENT: New record from: " + Source.getString(treatment.source) + " " + treatment.toString()); + DatabaseHelper.updateEarliestDataChange(treatment.date); + scheduleTreatmentChange(treatment); + return true; + } + if (treatment.source == Source.USER) { + getDao().create(treatment); + log.debug("TREATMENT: New record from: " + Source.getString(treatment.source) + " " + treatment.toString()); + DatabaseHelper.updateEarliestDataChange(treatment.date); + scheduleTreatmentChange(treatment); + return true; + } + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return false; + } + + public void deleteNS(JSONObject json) { + String _id = JsonHelper.safeGetString(json, "_id"); + if (_id != null && !_id.isEmpty()) + this.deleteByNSId(_id); + } + + /** + * deletes an entry by its NS Id. + *

+ * Basically a convenience method for findByNSId and delete. + * + * @param _id + */ + private void deleteByNSId(String _id) { + Treatment stored = findByNSId(_id); + if (stored != null) { + log.debug("TREATMENT: Removing Treatment record from database: " + stored.toString()); + delete(stored); + DatabaseHelper.updateEarliestDataChange(stored.date); + scheduleTreatmentChange(null); + } + } + + /** + * deletes the treatment and sends the treatmentChange Event + *

+ * should be moved ot a Service + * + * @param treatment + */ + public void delete(Treatment treatment) { + try { + getDao().delete(treatment); + DatabaseHelper.updateEarliestDataChange(treatment.date); + this.scheduleTreatmentChange(treatment); + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + } + + public void update(Treatment treatment) { + try { + getDao().update(treatment); + DatabaseHelper.updateEarliestDataChange(treatment.date); + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + scheduleTreatmentChange(treatment); + } + + /** + * finds treatment by its NS Id. + * + * @param _id + * @return + */ + @Nullable + public Treatment findByNSId(String _id) { + try { + Dao daoTreatments = getDao(); + QueryBuilder queryBuilder = daoTreatments.queryBuilder(); + Where where = queryBuilder.where(); + where.eq("_id", _id); + queryBuilder.limit(10L); + PreparedQuery preparedQuery = queryBuilder.prepare(); + List trList = daoTreatments.query(preparedQuery); + if (trList.size() != 1) { + //log.debug("Treatment findTreatmentById query size: " + trList.size()); + return null; + } else { + //log.debug("Treatment findTreatmentById found: " + trList.get(0).log()); + return trList.get(0); + } + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return null; + } + + public List getTreatmentDataFromTime(long mills, boolean ascending) { + try { + Dao daoTreatments = getDao(); + List treatments; + QueryBuilder queryBuilder = daoTreatments.queryBuilder(); + queryBuilder.orderBy("date", ascending); + Where where = queryBuilder.where(); + where.ge("date", mills); + PreparedQuery preparedQuery = queryBuilder.prepare(); + treatments = daoTreatments.query(preparedQuery); + return treatments; + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return new ArrayList<>(); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsFragment.java index b10ad96697..fe79f8555b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsFragment.java @@ -8,7 +8,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; import com.squareup.otto.Subscribe; import org.slf4j.Logger; @@ -25,6 +24,7 @@ import info.nightscout.androidaps.plugins.Treatments.fragments.TreatmentsExtende import info.nightscout.androidaps.plugins.Treatments.fragments.TreatmentsProfileSwitchFragment; import info.nightscout.androidaps.plugins.Treatments.fragments.TreatmentsTempTargetFragment; import info.nightscout.androidaps.plugins.Treatments.fragments.TreatmentsTemporaryBasalsFragment; +import info.nightscout.utils.FabricPrivacy; public class TreatmentsFragment extends SubscriberFragment implements View.OnClickListener { private static Logger log = LoggerFactory.getLogger(TreatmentsFragment.class); @@ -60,7 +60,7 @@ public class TreatmentsFragment extends SubscriberFragment implements View.OnCli return view; } catch (Exception e) { - Crashlytics.logException(e); + FabricPrivacy.logException(e); } return null; @@ -124,7 +124,7 @@ public class TreatmentsFragment extends SubscriberFragment implements View.OnCli @Override protected void updateGUI() { if (ConfigBuilderPlugin.getActivePump().getPumpDescription().isExtendedBolusCapable - || MainApp.getConfigBuilder().getExtendedBolusesFromHistory().size() > 0) { + || TreatmentsPlugin.getPlugin().getExtendedBolusesFromHistory().size() > 0) { extendedBolusesTab.setVisibility(View.VISIBLE); } else { extendedBolusesTab.setVisibility(View.GONE); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java index 900eba7a40..55129cdc7b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java @@ -8,9 +8,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.Date; import java.util.List; -import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; @@ -27,22 +27,30 @@ import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.ProfileSwitch; import info.nightscout.androidaps.db.TempTarget; import info.nightscout.androidaps.db.TemporaryBasal; -import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventReloadProfileSwitchData; import info.nightscout.androidaps.events.EventReloadTempBasalData; import info.nightscout.androidaps.events.EventReloadTreatmentData; import info.nightscout.androidaps.events.EventTempTargetChange; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.TreatmentsInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensData; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; +import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; +import info.nightscout.androidaps.plugins.SensitivityAAPS.SensitivityAAPSPlugin; +import info.nightscout.androidaps.plugins.SensitivityWeightedAverage.SensitivityWeightedAveragePlugin; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.NSUpload; import info.nightscout.utils.SP; +import info.nightscout.utils.T; /** * Created by mike on 05.08.2016. */ -public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { +public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface { private static Logger log = LoggerFactory.getLogger(TreatmentsPlugin.class); private static TreatmentsPlugin treatmentsPlugin; @@ -53,126 +61,95 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { return treatmentsPlugin; } + private TreatmentService service; + private IobTotal lastTreatmentCalculation; private IobTotal lastTempBasalsCalculation; - public static List treatments; - private static Intervals tempBasals = new NonOverlappingIntervals<>(); - private static Intervals extendedBoluses = new NonOverlappingIntervals<>(); - private static Intervals tempTargets = new OverlappingIntervals<>(); - private static ProfileIntervals profiles = new ProfileIntervals<>(); + private final ArrayList treatments = new ArrayList<>(); + private final Intervals tempBasals = new NonOverlappingIntervals<>(); + private final Intervals extendedBoluses = new NonOverlappingIntervals<>(); + private final Intervals tempTargets = new OverlappingIntervals<>(); + private final ProfileIntervals profiles = new ProfileIntervals<>(); - private boolean fragmentEnabled = true; - private boolean fragmentVisible = true; - - @Override - public String getFragmentClass() { - return TreatmentsFragment.class.getName(); + public TreatmentsPlugin() { + super(new PluginDescription() + .mainType(PluginType.TREATMENT) + .fragmentClass(TreatmentsFragment.class.getName()) + .pluginName(R.string.treatments) + .shortName(R.string.treatments_shortname) + .preferencesId(R.xml.pref_absorption_oref0) + .alwaysEnabled(true) + ); + this.service = new TreatmentService(); } @Override - public String getName() { - return MainApp.instance().getString(R.string.treatments); - } - - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.treatments_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == TREATMENT && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == TREATMENT && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return !Config.NSCLIENT && !Config.G5UPLOADER; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == TREATMENT) this.fragmentEnabled = fragmentEnabled; - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == TREATMENT) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return -1; - } - - @Override - public int getType() { - return PluginBase.TREATMENT; - } - - private TreatmentsPlugin() { + protected void onStart() { MainApp.bus().register(this); initializeTempBasalData(); initializeTreatmentData(); initializeExtendedBolusData(); initializeTempTargetData(); initializeProfileSwitchData(); + super.onStart(); } - private static void initializeTreatmentData() { - // Treatments - double dia = MainApp.getConfigBuilder() == null ? Constants.defaultDIA : MainApp.getConfigBuilder().getProfile().getDia(); - long fromMills = (long) (System.currentTimeMillis() - 60 * 60 * 1000L * (24 + dia)); - - treatments = MainApp.getDbHelper().getTreatmentDataFromTime(fromMills, false); + @Override + protected void onStop() { + MainApp.bus().register(this); } - private static void initializeTempBasalData() { - // Treatments - double dia = MainApp.getConfigBuilder() == null ? Constants.defaultDIA : MainApp.getConfigBuilder().getProfile().getDia(); + public TreatmentService getService() { + return this.service; + } + + private void initializeTreatmentData() { + double dia = Constants.defaultDIA; + if (MainApp.getConfigBuilder() != null && MainApp.getConfigBuilder().getProfile() != null) + dia = MainApp.getConfigBuilder().getProfile().getDia(); + long fromMills = (long) (System.currentTimeMillis() - 60 * 60 * 1000L * (24 + dia)); + synchronized (treatments) { + treatments.clear(); + treatments.addAll(getService().getTreatmentDataFromTime(fromMills, false)); + } + } + + private void initializeTempBasalData() { + double dia = Constants.defaultDIA; + if (MainApp.getConfigBuilder() != null && MainApp.getConfigBuilder().getProfile() != null) + dia = MainApp.getConfigBuilder().getProfile().getDia(); long fromMills = (long) (System.currentTimeMillis() - 60 * 60 * 1000L * (24 + dia)); - tempBasals.reset().add(MainApp.getDbHelper().getTemporaryBasalsDataFromTime(fromMills, false)); + synchronized (tempBasals) { + tempBasals.reset().add(MainApp.getDbHelper().getTemporaryBasalsDataFromTime(fromMills, false)); + } } - private static void initializeExtendedBolusData() { - // Treatments - double dia = MainApp.getConfigBuilder() == null ? Constants.defaultDIA : MainApp.getConfigBuilder().getProfile().getDia(); + private void initializeExtendedBolusData() { + double dia = Constants.defaultDIA; + if (MainApp.getConfigBuilder() != null && MainApp.getConfigBuilder().getProfile() != null) + dia = MainApp.getConfigBuilder().getProfile().getDia(); long fromMills = (long) (System.currentTimeMillis() - 60 * 60 * 1000L * (24 + dia)); - extendedBoluses.reset().add(MainApp.getDbHelper().getExtendedBolusDataFromTime(fromMills, false)); + synchronized (extendedBoluses) { + extendedBoluses.reset().add(MainApp.getDbHelper().getExtendedBolusDataFromTime(fromMills, false)); + } } private void initializeTempTargetData() { - long fromMills = System.currentTimeMillis() - 60 * 60 * 1000L * 24; - tempTargets.reset().add(MainApp.getDbHelper().getTemptargetsDataFromTime(fromMills, false)); + synchronized (tempTargets) { + long fromMills = System.currentTimeMillis() - 60 * 60 * 1000L * 24; + tempTargets.reset().add(MainApp.getDbHelper().getTemptargetsDataFromTime(fromMills, false)); + } } private void initializeProfileSwitchData() { - profiles.reset().add(MainApp.getDbHelper().getProfileSwitchData(false)); + synchronized (profiles) { + profiles.reset().add(MainApp.getDbHelper().getProfileSwitchData(false)); + } } @Override @@ -190,23 +167,24 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { double dia = profile.getDia(); - for (Integer pos = 0; pos < treatments.size(); pos++) { - Treatment t = treatments.get(pos); - if (!t.isValid) continue; - if (t.date > time) continue; - Iob tIOB = t.iobCalc(time, dia); - total.iob += tIOB.iobContrib; - total.activity += tIOB.activityContrib; - if (!t.isSMB) { - // instead of dividing the DIA that only worked on the bilinear curves, - // multiply the time the treatment is seen active. - long timeSinceTreatment = time - t.date; - long snoozeTime = t.date + (long) (timeSinceTreatment * SP.getDouble("openapsama_bolussnooze_dia_divisor", 2.0)); - Iob bIOB = t.iobCalc(snoozeTime, dia); - total.bolussnooze += bIOB.iobContrib; - } else { - total.basaliob += t.insulin; - total.microBolusIOB += tIOB.iobContrib; + synchronized (treatments) { + for (Integer pos = 0; pos < treatments.size(); pos++) { + Treatment t = treatments.get(pos); + if (!t.isValid) continue; + if (t.date > time) continue; + Iob tIOB = t.iobCalc(time, dia); + total.iob += tIOB.iobContrib; + total.activity += tIOB.activityContrib; + if (t.insulin > 0 && t.date > total.lastBolusTime) + total.lastBolusTime = t.date; + if (!t.isSMB) { + // instead of dividing the DIA that only worked on the bilinear curves, + // multiply the time the treatment is seen active. + long timeSinceTreatment = time - t.date; + long snoozeTime = t.date + (long) (timeSinceTreatment * SP.getDouble(R.string.key_openapsama_bolussnooze_dia_divisor, 2.0)); + Iob bIOB = t.iobCalc(snoozeTime, dia); + total.bolussnooze += bIOB.iobContrib; + } } } @@ -235,45 +213,83 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { if (profile == null) return result; long now = System.currentTimeMillis(); - long dia_ago = now - (Double.valueOf(1.5d * profile.getDia() * 60 * 60 * 1000l)).longValue(); + long dia_ago = now - (Double.valueOf(profile.getDia() * T.hours(1).msecs())).longValue(); - for (Treatment treatment : treatments) { - if (!treatment.isValid) - continue; - long t = treatment.date; - if (t > dia_ago && t <= now) { - if (treatment.carbs >= 1) { - result.carbs += treatment.carbs; + double maxAbsorptionHours = Constants.DEFAULT_MAX_ABSORPTION_TIME; + if (SensitivityAAPSPlugin.getPlugin().isEnabled(PluginType.SENSITIVITY) || SensitivityWeightedAveragePlugin.getPlugin().isEnabled(PluginType.SENSITIVITY)) { + maxAbsorptionHours = SP.getDouble(R.string.key_absorption_maxtime, Constants.DEFAULT_MAX_ABSORPTION_TIME); + } else { + maxAbsorptionHours = SP.getDouble(R.string.key_absorption_cutoff, Constants.DEFAULT_MAX_ABSORPTION_TIME); + } + long absorptionTime_ago = now - (Double.valueOf(maxAbsorptionHours * T.hours(1).msecs())).longValue(); + + synchronized (treatments) { + for (Treatment treatment : treatments) { + if (!treatment.isValid) + continue; + long t = treatment.date; + + if (t > dia_ago && t <= now) { + if (treatment.insulin > 0 && treatment.mealBolus) { + result.boluses += treatment.insulin; + } } - if (treatment.insulin > 0 && treatment.mealBolus) { - result.boluses += treatment.insulin; + + if (t > absorptionTime_ago && t <= now) { + if (treatment.carbs >= 1) { + result.carbs += treatment.carbs; + if(t > result.lastCarbTime) + result.lastCarbTime = t; + } } } } - AutosensData autosensData = IobCobCalculatorPlugin.getLastAutosensDataSynchronized("getMealData()"); + AutosensData autosensData = IobCobCalculatorPlugin.getPlugin().getLastAutosensDataSynchronized("getMealData()"); if (autosensData != null) { result.mealCOB = autosensData.cob; + result.slopeFromMinDeviation = autosensData.slopeFromMinDeviation; + result.slopeFromMaxDeviation = autosensData.slopeFromMaxDeviation; + result.usedMinCarbsImpact = autosensData.usedMinCarbsImpact; } + result.lastBolusTime = getLastBolusTime(); return result; } @Override public List getTreatmentsFromHistory() { - return treatments; + synchronized (treatments) { + return new ArrayList<>(treatments); + } } @Override public List getTreatments5MinBackFromHistory(long time) { List in5minback = new ArrayList<>(); - for (Integer pos = 0; pos < treatments.size(); pos++) { - Treatment t = treatments.get(pos); - if (!t.isValid) - continue; - if (t.date <= time && t.date > time - 5 * 60 * 1000 && t.carbs > 0) - in5minback.add(t); + synchronized (treatments) { + for (Integer pos = 0; pos < treatments.size(); pos++) { + Treatment t = treatments.get(pos); + if (!t.isValid) + continue; + if (t.date <= time && t.date > time - 5 * 60 * 1000 && t.carbs > 0) + in5minback.add(t); + } + return in5minback; } - return in5minback; + } + + @Override + public long getLastBolusTime() { + long now = System.currentTimeMillis(); + long last = 0; + synchronized (treatments) { + for (Treatment t : treatments) { + if (t.date > last && t.insulin > 0 && t.isValid && t.date <= now) + last = t.date; + } + } + log.debug("Last bolus time: " + new Date(last).toLocaleString()); + return last; } @Override @@ -283,7 +299,9 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { @Override public TemporaryBasal getRealTempBasalFromHistory(long time) { - return tempBasals.getValueByInterval(time); + synchronized (tempBasals) { + return tempBasals.getValueByInterval(time); + } } @Override @@ -306,6 +324,7 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { } @Subscribe + @SuppressWarnings("unused") public void onStatusEvent(final EventReloadTempBasalData ev) { log.debug("EventReloadTempBasalData"); initializeTempBasalData(); @@ -318,13 +337,25 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { } @Override - public IobTotal getCalculationToTimeTempBasals(long time) { + public IobTotal getCalculationToTimeTempBasals(long time, Profile profile) { + return getCalculationToTimeTempBasals(time, profile, false, 0); + } + + public IobTotal getCalculationToTimeTempBasals(long time, Profile profile, boolean truncate, long truncateTime) { IobTotal total = new IobTotal(time); synchronized (tempBasals) { for (Integer pos = 0; pos < tempBasals.size(); pos++) { TemporaryBasal t = tempBasals.get(pos); if (t.date > time) continue; - IobTotal calc = t.iobCalc(time); + IobTotal calc; + if(truncate && t.end() > truncateTime){ + TemporaryBasal dummyTemp = new TemporaryBasal(); + dummyTemp.copyFrom(t); + dummyTemp.cutEndTo(truncateTime); + calc = dummyTemp.iobCalc(time, profile); + } else { + calc = t.iobCalc(time, profile); + } //log.debug("BasalIOB " + new Date(time) + " >>> " + calc.basaliob); total.plus(calc); } @@ -335,7 +366,15 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { for (Integer pos = 0; pos < extendedBoluses.size(); pos++) { ExtendedBolus e = extendedBoluses.get(pos); if (e.date > time) continue; - IobTotal calc = e.iobCalc(time); + IobTotal calc; + if(truncate && e.end() > truncateTime){ + ExtendedBolus dummyExt = new ExtendedBolus(); + dummyExt.copyFrom(e); + dummyExt.cutEndTo(truncateTime); + calc = dummyExt.iobCalc(time); + } else { + calc = e.iobCalc(time); + } totalExt.plus(calc); } } @@ -351,7 +390,9 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { @Override public void updateTotalIOBTempBasals() { - lastTempBasalsCalculation = getCalculationToTimeTempBasals(System.currentTimeMillis()); + Profile profile = MainApp.getConfigBuilder().getProfile(); + if (profile != null) + lastTempBasalsCalculation = getCalculationToTimeTempBasals(DateUtil.now(), profile); } @Nullable @@ -368,59 +409,59 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { @Override public ExtendedBolus getExtendedBolusFromHistory(long time) { - return extendedBoluses.getValueByInterval(time); + synchronized (extendedBoluses) { + return extendedBoluses.getValueByInterval(time); + } } @Override public boolean addToHistoryExtendedBolus(ExtendedBolus extendedBolus) { //log.debug("Adding new ExtentedBolus record" + extendedBolus.log()); - return MainApp.getDbHelper().createOrUpdate(extendedBolus); + boolean newRecordCreated = MainApp.getDbHelper().createOrUpdate(extendedBolus); + if (newRecordCreated) { + if (extendedBolus.durationInMinutes == 0) { + if (MainApp.getConfigBuilder().getActivePump().isFakingTempsByExtendedBoluses()) + NSUpload.uploadTempBasalEnd(extendedBolus.date, true, extendedBolus.pumpId); + else + NSUpload.uploadExtendedBolusEnd(extendedBolus.date, extendedBolus.pumpId); + } else if (MainApp.getConfigBuilder().getActivePump().isFakingTempsByExtendedBoluses()) + NSUpload.uploadTempBasalStartAbsolute(new TemporaryBasal(extendedBolus), extendedBolus.insulin); + else + NSUpload.uploadExtendedBolus(extendedBolus); + } + return newRecordCreated; } @Override public Intervals getExtendedBolusesFromHistory() { - return extendedBoluses; - } - - @Override - public double getTempBasalAbsoluteRateHistory() { - TemporaryBasal tb = getTempBasalFromHistory(System.currentTimeMillis()); - if (tb != null) { - if (tb.isFakeExtended) { - double baseRate = ConfigBuilderPlugin.getActivePump().getBaseBasalRate(); - double tempRate = baseRate + tb.netExtendedRate; - return tempRate; - } else if (tb.isAbsolute) { - return tb.absoluteRate; - } else { - double baseRate = ConfigBuilderPlugin.getActivePump().getBaseBasalRate(); - double tempRate = baseRate * (tb.percentRate / 100d); - return tempRate; - } + synchronized (extendedBoluses) { + return new NonOverlappingIntervals<>(extendedBoluses); } - return 0; - } - - @Override - public double getTempBasalRemainingMinutesFromHistory() { - TemporaryBasal activeTemp = getTempBasalFromHistory(System.currentTimeMillis()); - if (activeTemp != null) { - return activeTemp.getPlannedRemainingMinutes(); - } - return 0; } @Override public Intervals getTemporaryBasalsFromHistory() { - return tempBasals; + synchronized (tempBasals) { + return new NonOverlappingIntervals<>(tempBasals); + } } @Override public boolean addToHistoryTempBasal(TemporaryBasal tempBasal) { //log.debug("Adding new TemporaryBasal record" + tempBasal.toString()); - return MainApp.getDbHelper().createOrUpdate(tempBasal); + boolean newRecordCreated = MainApp.getDbHelper().createOrUpdate(tempBasal); + if (newRecordCreated) { + if (tempBasal.durationInMinutes == 0) + NSUpload.uploadTempBasalEnd(tempBasal.date, false, tempBasal.pumpId); + else if (tempBasal.isAbsolute) + NSUpload.uploadTempBasalStartAbsolute(tempBasal, null); + else + NSUpload.uploadTempBasalStartPercent(tempBasal); + } + return newRecordCreated; } + // return true if new record is created @Override public boolean addToHistoryTreatment(DetailedBolusInfo detailedBolusInfo) { Treatment treatment = new Treatment(); @@ -434,7 +475,7 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { treatment.carbs = detailedBolusInfo.carbs; treatment.source = detailedBolusInfo.source; treatment.mealBolus = treatment.carbs > 0; - boolean newRecordCreated = MainApp.getDbHelper().createOrUpdate(treatment); + boolean newRecordCreated = getService().createOrUpdate(treatment); //log.debug("Adding new Treatment record" + treatment.toString()); if (detailedBolusInfo.carbTime != 0) { Treatment carbsTreatment = new Treatment(); @@ -443,9 +484,11 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { carbsTreatment.date = detailedBolusInfo.date + detailedBolusInfo.carbTime * 60 * 1000L + 1000L; // add 1 sec to make them different records carbsTreatment.carbs = detailedBolusInfo.carbs; carbsTreatment.source = detailedBolusInfo.source; - MainApp.getDbHelper().createOrUpdate(carbsTreatment); + getService().createOrUpdate(carbsTreatment); //log.debug("Adding new Treatment record" + carbsTreatment); } + if (newRecordCreated && detailedBolusInfo.isValid) + NSUpload.uploadTreatmentRecord(detailedBolusInfo); return newRecordCreated; } @@ -470,6 +513,7 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { // TempTargets @Subscribe + @SuppressWarnings("unused") public void onStatusEvent(final EventTempTargetChange ev) { initializeTempTargetData(); } @@ -477,40 +521,60 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { @Nullable @Override public TempTarget getTempTargetFromHistory() { - return tempTargets.getValueByInterval(System.currentTimeMillis()); + synchronized (tempTargets) { + return tempTargets.getValueByInterval(System.currentTimeMillis()); + } } @Nullable @Override public TempTarget getTempTargetFromHistory(long time) { - return tempTargets.getValueByInterval(time); + synchronized (tempTargets) { + return tempTargets.getValueByInterval(time); + } } @Override public Intervals getTempTargetsFromHistory() { - return tempTargets; + synchronized (tempTargets) { + return new OverlappingIntervals<>(tempTargets); + } + } + + @Override + public void addToHistoryTempTarget(TempTarget tempTarget) { + //log.debug("Adding new TemporaryBasal record" + profileSwitch.log()); + MainApp.getDbHelper().createOrUpdate(tempTarget); + NSUpload.uploadTempTarget(tempTarget); } // Profile Switch @Subscribe + @SuppressWarnings("unused") public void onStatusEvent(final EventReloadProfileSwitchData ev) { initializeProfileSwitchData(); } @Override public ProfileSwitch getProfileSwitchFromHistory(long time) { - return (ProfileSwitch) profiles.getValueToTime(time); + synchronized (profiles) { + return (ProfileSwitch) profiles.getValueToTime(time); + } } @Override public ProfileIntervals getProfileSwitchesFromHistory() { - return profiles; + synchronized (profiles) { + return new ProfileIntervals<>(profiles); + } } @Override public void addToHistoryProfileSwitch(ProfileSwitch profileSwitch) { //log.debug("Adding new TemporaryBasal record" + profileSwitch.log()); + MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_SWITCH_MISSING)); MainApp.getDbHelper().createOrUpdate(profileSwitch); + NSUpload.uploadProfileSwitch(profileSwitch); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/ProfileGraph.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/ProfileGraph.java new file mode 100644 index 0000000000..99019e5fd7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/ProfileGraph.java @@ -0,0 +1,57 @@ +package info.nightscout.androidaps.plugins.Treatments.fragments; + +import android.content.Context; +import android.util.AttributeSet; + +import com.jjoe64.graphview.GraphView; +import com.jjoe64.graphview.series.DataPoint; +import com.jjoe64.graphview.series.LineGraphSeries; + +import java.util.ArrayList; +import java.util.List; + +import info.nightscout.androidaps.data.Profile; +import info.nightscout.utils.Round; + +/** + * Created by Adrian on 15.04.2018. + */ + +public class ProfileGraph extends GraphView { + + public ProfileGraph(Context context) { + super(context); + } + + public ProfileGraph(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void show(Profile profile) { + removeAllSeries(); + + LineGraphSeries basalSeries = null; + List basalArray = new ArrayList<>(); + + for (int hour = 0; hour < 24; hour++) { + basalArray.add(new DataPoint(hour, profile.getBasalTimeFromMidnight(new Integer(hour*60*60)))); + basalArray.add(new DataPoint(hour+1, profile.getBasalTimeFromMidnight(new Integer(hour*60*60)))); + } + DataPoint[] basalDataPoints = new DataPoint[basalArray.size()]; + basalDataPoints = basalArray.toArray(basalDataPoints); + addSeries(basalSeries = new LineGraphSeries<>(basalDataPoints)); + basalSeries.setThickness(8); + basalSeries.setDrawBackground(true); + + getViewport().setXAxisBoundsManual(true); + getViewport().setMinX(0); + getViewport().setMaxX(24); + + getViewport().setYAxisBoundsManual(true); + getViewport().setMinY(0); + getViewport().setMaxY(Round.ceilTo(profile.getMaxDailyBasal()*1.1, 0.5)); + + getGridLabelRenderer().setNumHorizontalLabels(13); + getGridLabelRenderer().setVerticalLabelsColor(basalSeries.getColor()); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/ProfileViewerDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/ProfileViewerDialog.java index a34146f04a..5f758c8a06 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/ProfileViewerDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/ProfileViewerDialog.java @@ -12,11 +12,14 @@ import android.widget.TextView; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import info.nightscout.androidaps.MainApp; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.ProfileSwitch; import info.nightscout.androidaps.plugins.PumpDanaR.Dialogs.ProfileViewDialog; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; @@ -30,18 +33,36 @@ public class ProfileViewerDialog extends DialogFragment { private static Logger log = LoggerFactory.getLogger(ProfileViewDialog.class); - private TextView noProfile; - private TextView units; - private TextView dia; - private TextView activeProfile; - private TextView ic; - private TextView isf; - private TextView basal; - private TextView target; - private View dateDelimiter; - private LinearLayout dateLayout; - private TextView dateTextView; - private Button refreshButton; + @BindView(R.id.profileview_noprofile) + TextView noProfile; + @BindView(R.id.profileview_invalidprofile) + TextView invalidProfile; + @BindView(R.id.profileview_units) + TextView units; + @BindView(R.id.profileview_dia) + TextView dia; + @BindView(R.id.profileview_activeprofile) + TextView activeProfile; + @BindView(R.id.profileview_ic) + TextView ic; + @BindView(R.id.profileview_isf) + TextView isf; + @BindView(R.id.profileview_basal) + TextView basal; + @BindView(R.id.profileview_target) + TextView target; + @BindView(R.id.profileview_datedelimiter) + View dateDelimiter; + @BindView(R.id.profileview_datelayout) + LinearLayout dateLayout; + @BindView(R.id.profileview_date) + TextView dateTextView; + @BindView(R.id.profileview_reload) + Button refreshButton; + @BindView(R.id.basal_graph) + ProfileGraph basalGraph; + + private Unbinder unbinder; public static ProfileViewerDialog newInstance(long time) { ProfileViewerDialog dialog = new ProfileViewerDialog(); @@ -60,35 +81,41 @@ public class ProfileViewerDialog extends DialogFragment { time = getArguments().getLong("time"); } + @Override + public void onDestroyView() { + super.onDestroyView(); + if (unbinder != null) + unbinder.unbind(); + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View layout = inflater.inflate(R.layout.profileviewer_fragment, container, false); + View view = inflater.inflate(R.layout.profileviewer_fragment, container, false); + + unbinder = ButterKnife.bind(this, view); - noProfile = (TextView) layout.findViewById(R.id.profileview_noprofile); - units = (TextView) layout.findViewById(R.id.profileview_units); - dia = (TextView) layout.findViewById(R.id.profileview_dia); - activeProfile = (TextView) layout.findViewById(R.id.profileview_activeprofile); - ic = (TextView) layout.findViewById(R.id.profileview_ic); - isf = (TextView) layout.findViewById(R.id.profileview_isf); - basal = (TextView) layout.findViewById(R.id.profileview_basal); - target = (TextView) layout.findViewById(R.id.profileview_target); - refreshButton = (Button) layout.findViewById(R.id.profileview_reload); refreshButton.setVisibility(View.GONE); - dateDelimiter = layout.findViewById(R.id.profileview_datedelimiter); dateDelimiter.setVisibility(View.VISIBLE); - dateLayout = (LinearLayout) layout.findViewById(R.id.profileview_datelayout); dateLayout.setVisibility(View.VISIBLE); - dateTextView = (TextView) layout.findViewById(R.id.profileview_date); setContent(); - return layout; + return view; + } + + @Override + public void onResume() { + ViewGroup.LayoutParams params = getDialog().getWindow().getAttributes(); + params.width = ViewGroup.LayoutParams.MATCH_PARENT; + params.height = ViewGroup.LayoutParams.MATCH_PARENT; + getDialog().getWindow().setAttributes((android.view.WindowManager.LayoutParams) params); + super.onResume(); } private void setContent() { Profile profile = null; - ProfileSwitch profileSwitch = MainApp.getConfigBuilder().getProfileSwitchFromHistory(time); - if(profileSwitch!=null && profileSwitch.profileJson != null){ + ProfileSwitch profileSwitch = TreatmentsPlugin.getPlugin().getProfileSwitchFromHistory(time); + if (profileSwitch != null && profileSwitch.profileJson != null) { profile = profileSwitch.getProfileObject(); } if (profile != null) { @@ -101,6 +128,12 @@ public class ProfileViewerDialog extends DialogFragment { isf.setText(profile.getIsfList()); basal.setText(profile.getBasalList()); target.setText(profile.getTargetList()); + basalGraph.show(profile); + + if (profile.isValid("ProfileViewDialog")) + invalidProfile.setVisibility(View.GONE); + else + invalidProfile.setVisibility(View.VISIBLE); } else { noProfile.setVisibility(View.VISIBLE); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsBolusFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsBolusFragment.java index d096fd8549..ae977d4024 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsBolusFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsBolusFragment.java @@ -17,7 +17,6 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import com.squareup.otto.Subscribe; @@ -32,12 +31,13 @@ import info.nightscout.androidaps.Services.Intents; import info.nightscout.androidaps.data.Iob; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.Source; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.plugins.Treatments.Treatment; import info.nightscout.androidaps.events.EventNewBG; import info.nightscout.androidaps.events.EventTreatmentChange; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; import info.nightscout.androidaps.plugins.NSClientInternal.UploadQueue; import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.NSUpload; @@ -76,12 +76,12 @@ public class TreatmentsBolusFragment extends SubscriberFragment implements View. return; Treatment t = treatments.get(position); holder.date.setText(DateUtil.dateAndTimeString(t.date)); - holder.insulin.setText(DecimalFormatter.to2Decimal(t.insulin) + " U"); + holder.insulin.setText(DecimalFormatter.toPumpSupportedBolus(t.insulin) + " U"); holder.carbs.setText(DecimalFormatter.to0Decimal(t.carbs) + " g"); Iob iob = t.iobCalc(System.currentTimeMillis(), profile.getDia()); holder.iob.setText(DecimalFormatter.to2Decimal(iob.iobContrib) + " U"); holder.activity.setText(DecimalFormatter.to3Decimal(iob.activityContrib) + " U"); - holder.mealOrCorrection.setText(t.mealBolus ? MainApp.sResources.getString(R.string.mealbolus) : MainApp.sResources.getString(R.string.correctionbous)); + holder.mealOrCorrection.setText(t.isSMB ? "SMB" : t.mealBolus ? MainApp.sResources.getString(R.string.mealbolus) : MainApp.sResources.getString(R.string.correctionbous)); holder.ph.setVisibility(t.source == Source.PUMP ? View.VISIBLE : View.GONE); holder.ns.setVisibility(NSUpload.isIdValid(t._id) ? View.VISIBLE : View.GONE); holder.invalid.setVisibility(t.isValid ? View.GONE : View.VISIBLE); @@ -89,6 +89,10 @@ public class TreatmentsBolusFragment extends SubscriberFragment implements View. holder.iob.setTextColor(ContextCompat.getColor(MainApp.instance(), R.color.colorActive)); else holder.iob.setTextColor(holder.carbs.getCurrentTextColor()); + if (t.date > DateUtil.now()) + holder.date.setTextColor(ContextCompat.getColor(MainApp.instance(), R.color.colorScheduled)); + else + holder.date.setTextColor(holder.carbs.getCurrentTextColor()); holder.remove.setTag(t); } @@ -145,17 +149,17 @@ public class TreatmentsBolusFragment extends SubscriberFragment implements View. final String _id = treatment._id; if (treatment.source == Source.PUMP) { treatment.isValid = false; - MainApp.getDbHelper().update(treatment); + TreatmentsPlugin.getPlugin().getService().update(treatment); } else { if (NSUpload.isIdValid(_id)) { NSUpload.removeCareportalEntryFromNS(_id); } else { UploadQueue.removeID("dbAdd", _id); } - MainApp.getDbHelper().delete(treatment); + TreatmentsPlugin.getPlugin().getService().delete(treatment); } updateGUI(); - Answers.getInstance().logCustom(new CustomEvent("RemoveTreatment")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("RemoveTreatment")); } }); builder.setNegativeButton(MainApp.sResources.getString(R.string.cancel), null); @@ -176,7 +180,7 @@ public class TreatmentsBolusFragment extends SubscriberFragment implements View. llm = new LinearLayoutManager(view.getContext()); recyclerView.setLayoutManager(llm); - RecyclerViewAdapter adapter = new RecyclerViewAdapter(TreatmentsPlugin.treatments); + RecyclerViewAdapter adapter = new RecyclerViewAdapter(TreatmentsPlugin.getPlugin().getTreatmentsFromHistory()); recyclerView.setAdapter(adapter); iobTotal = (TextView) view.findViewById(R.id.treatments_iobtotal); @@ -204,7 +208,7 @@ public class TreatmentsBolusFragment extends SubscriberFragment implements View. builder.setMessage(this.getContext().getString(R.string.refresheventsfromnightscout) + "?"); builder.setPositiveButton(this.getContext().getString(R.string.ok), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { - MainApp.getDbHelper().resetTreatments(); + TreatmentsPlugin.getPlugin().getService().resetTreatments(); Intent restartNSClient = new Intent(Intents.ACTION_RESTART); MainApp.instance().getApplicationContext().sendBroadcast(restartNSClient); } @@ -232,7 +236,7 @@ public class TreatmentsBolusFragment extends SubscriberFragment implements View. activity.runOnUiThread(new Runnable() { @Override public void run() { - recyclerView.swapAdapter(new RecyclerViewAdapter(TreatmentsPlugin.treatments), false); + recyclerView.swapAdapter(new RecyclerViewAdapter(TreatmentsPlugin.getPlugin().getTreatmentsFromHistory()), false); if (TreatmentsPlugin.getPlugin().getLastCalculationTreatments() != null) { iobTotal.setText(DecimalFormatter.to2Decimal(TreatmentsPlugin.getPlugin().getLastCalculationTreatments().iob) + " U"); activityTotal.setText(DecimalFormatter.to3Decimal(TreatmentsPlugin.getPlugin().getLastCalculationTreatments().activity) + " U"); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsExtendedBolusesFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsExtendedBolusesFragment.java index 4e21259dae..c8fc539d02 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsExtendedBolusesFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsExtendedBolusesFragment.java @@ -5,7 +5,6 @@ import android.content.Context; import android.content.DialogInterface; import android.graphics.Paint; import android.os.Bundle; -import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.support.v7.widget.CardView; @@ -16,7 +15,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import com.squareup.otto.Subscribe; @@ -25,6 +23,7 @@ import org.slf4j.LoggerFactory; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.Intervals; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.Source; @@ -32,10 +31,11 @@ import info.nightscout.androidaps.events.EventExtendedBolusChange; import info.nightscout.androidaps.events.EventNewBG; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; import info.nightscout.androidaps.plugins.NSClientInternal.UploadQueue; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.NSUpload; -import info.nightscout.androidaps.data.Intervals; public class TreatmentsExtendedBolusesFragment extends SubscriberFragment { @@ -80,7 +80,7 @@ public class TreatmentsExtendedBolusesFragment extends SubscriberFragment { holder.date.setText(DateUtil.dateAndTimeString(extendedBolus.date) + " - " + DateUtil.timeString(extendedBolus.end())); } holder.duration.setText(DecimalFormatter.to0Decimal(extendedBolus.durationInMinutes) + " min"); - holder.insulin.setText(DecimalFormatter.to2Decimal(extendedBolus.insulin) + " U"); + holder.insulin.setText(DecimalFormatter.toPumpSupportedBolus(extendedBolus.insulin) + " U"); holder.realDuration.setText(DecimalFormatter.to0Decimal(extendedBolus.getRealDuration()) + " min"); IobTotal iob = extendedBolus.iobCalc(System.currentTimeMillis()); holder.iob.setText(DecimalFormatter.to2Decimal(iob.iob) + " U"); @@ -155,7 +155,7 @@ public class TreatmentsExtendedBolusesFragment extends SubscriberFragment { UploadQueue.removeID("dbAdd", _id); } MainApp.getDbHelper().delete(extendedBolus); - Answers.getInstance().logCustom(new CustomEvent("RemoveExtendedBolus")); + FabricPrivacy.getInstance().logCustom(new CustomEvent("RemoveExtendedBolus")); } }); builder.setNegativeButton(MainApp.sResources.getString(R.string.cancel), null); @@ -176,7 +176,7 @@ public class TreatmentsExtendedBolusesFragment extends SubscriberFragment { llm = new LinearLayoutManager(view.getContext()); recyclerView.setLayoutManager(llm); - RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainApp.getConfigBuilder().getExtendedBolusesFromHistory()); + RecyclerViewAdapter adapter = new RecyclerViewAdapter(TreatmentsPlugin.getPlugin().getExtendedBolusesFromHistory()); recyclerView.setAdapter(adapter); context = getContext(); @@ -202,7 +202,7 @@ public class TreatmentsExtendedBolusesFragment extends SubscriberFragment { activity.runOnUiThread(new Runnable() { @Override public void run() { - recyclerView.swapAdapter(new RecyclerViewAdapter(MainApp.getConfigBuilder().getExtendedBolusesFromHistory()), false); + recyclerView.swapAdapter(new RecyclerViewAdapter(TreatmentsPlugin.getPlugin().getExtendedBolusesFromHistory()), false); } }); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsProfileSwitchFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsProfileSwitchFragment.java index 21cf5e27d9..8a6b0456f5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsProfileSwitchFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsProfileSwitchFragment.java @@ -20,11 +20,12 @@ import android.widget.TextView; import com.squareup.otto.Subscribe; +import java.util.List; + import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.Services.Intents; import info.nightscout.androidaps.data.Profile; -import info.nightscout.androidaps.data.ProfileIntervals; import info.nightscout.androidaps.db.ProfileSwitch; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.events.EventProfileSwitchChange; @@ -49,9 +50,9 @@ public class TreatmentsProfileSwitchFragment extends SubscriberFragment implemen public class RecyclerViewAdapter extends RecyclerView.Adapter { - ProfileIntervals profileSwitchList; + List profileSwitchList; - RecyclerViewAdapter(ProfileIntervals profileSwitchList) { + RecyclerViewAdapter(List profileSwitchList) { this.profileSwitchList = profileSwitchList; } @@ -65,7 +66,7 @@ public class TreatmentsProfileSwitchFragment extends SubscriberFragment implemen public void onBindViewHolder(ProfileSwitchViewHolder holder, int position) { Profile profile = MainApp.getConfigBuilder().getProfile(); if (profile == null) return; - ProfileSwitch profileSwitch = profileSwitchList.getReversed(position); + ProfileSwitch profileSwitch = profileSwitchList.get(position); holder.ph.setVisibility(profileSwitch.source == Source.PUMP ? View.VISIBLE : View.GONE); holder.ns.setVisibility(NSUpload.isIdValid(profileSwitch._id) ? View.VISIBLE : View.GONE); @@ -148,7 +149,7 @@ public class TreatmentsProfileSwitchFragment extends SubscriberFragment implemen break; case R.id.profileswitch_date: case R.id.profileswitch_name: - long time = ((ProfileSwitch)v.getTag()).date; + long time = ((ProfileSwitch) v.getTag()).date; ProfileViewerDialog pvd = ProfileViewerDialog.newInstance(time); FragmentManager manager = getFragmentManager(); pvd.show(manager, "ProfileViewDialog"); @@ -168,7 +169,7 @@ public class TreatmentsProfileSwitchFragment extends SubscriberFragment implemen llm = new LinearLayoutManager(view.getContext()); recyclerView.setLayoutManager(llm); - RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainApp.getConfigBuilder().getProfileSwitchesFromHistory()); + RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainApp.getDbHelper().getProfileSwitchData(false)); recyclerView.setAdapter(adapter); refreshFromNS = (Button) view.findViewById(R.id.profileswitch_refreshfromnightscout); @@ -216,7 +217,7 @@ public class TreatmentsProfileSwitchFragment extends SubscriberFragment implemen activity.runOnUiThread(new Runnable() { @Override public void run() { - recyclerView.swapAdapter(new RecyclerViewAdapter(MainApp.getConfigBuilder().getProfileSwitchesFromHistory()), false); + recyclerView.swapAdapter(new RecyclerViewAdapter(MainApp.getDbHelper().getProfileSwitchData(false)), false); } }); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsTempTargetFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsTempTargetFragment.java index 61b6c05199..5ce79636b5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsTempTargetFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsTempTargetFragment.java @@ -6,7 +6,6 @@ import android.content.DialogInterface; import android.content.Intent; import android.graphics.Paint; import android.os.Bundle; -import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.support.v7.widget.CardView; @@ -23,15 +22,16 @@ import com.squareup.otto.Subscribe; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.Services.Intents; +import info.nightscout.androidaps.data.Intervals; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.db.TempTarget; import info.nightscout.androidaps.events.EventTempTargetChange; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; import info.nightscout.androidaps.plugins.NSClientInternal.UploadQueue; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.NSUpload; -import info.nightscout.androidaps.data.Intervals; import info.nightscout.utils.SP; /** @@ -84,16 +84,11 @@ public class TreatmentsTempTargetFragment extends SubscriberFragment implements holder.reasonLabel.setText(""); holder.reasonColon.setText(""); } - if (tempTarget.isInProgress()) { - if(tempTarget == currentlyActiveTarget){ - // active as newest - holder.date.setTextColor(ContextCompat.getColor(MainApp.instance(), R.color.colorInProgress)); - } else { - // other's that might become active again after the latest (overlapping) is over - holder.date.setTextColor(ContextCompat.getColor(MainApp.instance(), R.color.colorActive)); - } - } - else { + if (tempTarget.isInProgress() && tempTarget == currentlyActiveTarget) { + holder.date.setTextColor(ContextCompat.getColor(MainApp.instance(), R.color.colorActive)); + } else if (tempTarget.date > DateUtil.now()) { + holder.date.setTextColor(ContextCompat.getColor(MainApp.instance(), R.color.colorScheduled)); + } else { holder.date.setTextColor(holder.reasonColon.getCurrentTextColor()); } holder.remove.setTag(tempTarget); @@ -176,7 +171,7 @@ public class TreatmentsTempTargetFragment extends SubscriberFragment implements llm = new LinearLayoutManager(view.getContext()); recyclerView.setLayoutManager(llm); - RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainApp.getConfigBuilder().getTempTargetsFromHistory()); + RecyclerViewAdapter adapter = new RecyclerViewAdapter(TreatmentsPlugin.getPlugin().getTempTargetsFromHistory()); recyclerView.setAdapter(adapter); refreshFromNS = (Button) view.findViewById(R.id.temptargetrange_refreshfromnightscout); @@ -225,7 +220,7 @@ public class TreatmentsTempTargetFragment extends SubscriberFragment implements activity.runOnUiThread(new Runnable() { @Override public void run() { - recyclerView.swapAdapter(new RecyclerViewAdapter(MainApp.getConfigBuilder().getTempTargetsFromHistory()), false); + recyclerView.swapAdapter(new RecyclerViewAdapter(TreatmentsPlugin.getPlugin().getTempTargetsFromHistory()), false); } }); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsTemporaryBasalsFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsTemporaryBasalsFragment.java index bd6269e871..957c5c02f1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsTemporaryBasalsFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsTemporaryBasalsFragment.java @@ -2,10 +2,9 @@ package info.nightscout.androidaps.plugins.Treatments.fragments; import android.app.Activity; import android.content.Context; -import android.content.DialogInterface; import android.graphics.Paint; import android.os.Bundle; -import android.support.v4.app.Fragment; +import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.support.v7.widget.CardView; @@ -16,31 +15,28 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import com.squareup.otto.Subscribe; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.Intervals; import info.nightscout.androidaps.data.IobTotal; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.events.EventNewBG; import info.nightscout.androidaps.events.EventTempBasalChange; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; import info.nightscout.androidaps.plugins.NSClientInternal.UploadQueue; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.FabricPrivacy; import info.nightscout.utils.NSUpload; -import info.nightscout.androidaps.data.Intervals; public class TreatmentsTemporaryBasalsFragment extends SubscriberFragment { - private static Logger log = LoggerFactory.getLogger(TreatmentsTemporaryBasalsFragment.class); - RecyclerView recyclerView; LinearLayoutManager llm; @@ -69,7 +65,7 @@ public class TreatmentsTemporaryBasalsFragment extends SubscriberFragment { holder.ns.setVisibility(NSUpload.isIdValid(tempBasal._id) ? View.VISIBLE : View.GONE); if (tempBasal.isEndingEvent()) { holder.date.setText(DateUtil.dateAndTimeString(tempBasal.date)); - holder.duration.setText(MainApp.sResources.getString(R.string.cancel)); + holder.duration.setText(MainApp.gs(R.string.cancel)); holder.absolute.setText(""); holder.percent.setText(""); holder.realDuration.setText(""); @@ -81,29 +77,36 @@ public class TreatmentsTemporaryBasalsFragment extends SubscriberFragment { } else { if (tempBasal.isInProgress()) { holder.date.setText(DateUtil.dateAndTimeString(tempBasal.date)); + holder.date.setTextColor(ContextCompat.getColor(MainApp.instance(), R.color.colorActive)); } else { - holder.date.setText(DateUtil.dateAndTimeString(tempBasal.date) + " - " + DateUtil.timeString(tempBasal.end())); + holder.date.setText(DateUtil.dateAndTimeRangeString(tempBasal.date, tempBasal.end())); + holder.date.setTextColor(holder.netRatio.getCurrentTextColor()); } - holder.duration.setText(DecimalFormatter.to0Decimal(tempBasal.durationInMinutes) + " min"); + holder.duration.setText(DecimalFormatter.to0Decimal(tempBasal.durationInMinutes, " min")); if (tempBasal.isAbsolute) { - holder.absolute.setText(DecimalFormatter.to0Decimal(tempBasal.tempBasalConvertedToAbsolute(tempBasal.date)) + " U/h"); - holder.percent.setText(""); + Profile profile = MainApp.getConfigBuilder().getProfile(tempBasal.date); + if (profile != null) { + holder.absolute.setText(DecimalFormatter.to0Decimal(tempBasal.tempBasalConvertedToAbsolute(tempBasal.date, profile), " U/h")); + holder.percent.setText(""); + } else { + holder.absolute.setText(MainApp.gs(R.string.noprofile)); + holder.percent.setText(""); + } } else { holder.absolute.setText(""); - holder.percent.setText(DecimalFormatter.to0Decimal(tempBasal.percentRate) + "%"); + holder.percent.setText(DecimalFormatter.to0Decimal(tempBasal.percentRate, "%")); } - holder.realDuration.setText(DecimalFormatter.to0Decimal(tempBasal.getRealDuration()) + " min"); - IobTotal iob = tempBasal.iobCalc(System.currentTimeMillis()); - holder.iob.setText(DecimalFormatter.to2Decimal(iob.basaliob) + " U"); - holder.netInsulin.setText(DecimalFormatter.to2Decimal(iob.netInsulin) + " U"); - holder.netRatio.setText(DecimalFormatter.to2Decimal(iob.netRatio) + " U/h"); - //holder.extendedFlag.setVisibility(tempBasal.isExtended ? View.VISIBLE : View.GONE); + holder.realDuration.setText(DecimalFormatter.to0Decimal(tempBasal.getRealDuration(), " min")); + long now = DateUtil.now(); + IobTotal iob = new IobTotal(now); + Profile profile = MainApp.getConfigBuilder().getProfile(now); + if (profile != null) + iob = tempBasal.iobCalc(now, profile); + holder.iob.setText(DecimalFormatter.to2Decimal(iob.basaliob, " U")); + holder.netInsulin.setText(DecimalFormatter.to2Decimal(iob.netInsulin, " U")); + holder.netRatio.setText(DecimalFormatter.to2Decimal(iob.netRatio, " U/h")); holder.extendedFlag.setVisibility(View.GONE); - if (tempBasal.isInProgress()) - holder.date.setTextColor(ContextCompat.getColor(MainApp.instance(), R.color.colorActive)); - else - holder.date.setTextColor(holder.netRatio.getCurrentTextColor()); - if (tempBasal.iobCalc(System.currentTimeMillis()).basaliob != 0) + if (iob.basaliob != 0) holder.iob.setTextColor(ContextCompat.getColor(MainApp.instance(), R.color.colorActive)); else holder.iob.setTextColor(holder.netRatio.getCurrentTextColor()); @@ -161,21 +164,19 @@ public class TreatmentsTemporaryBasalsFragment extends SubscriberFragment { switch (v.getId()) { case R.id.tempbasals_remove: AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(MainApp.sResources.getString(R.string.confirmation)); - builder.setMessage(MainApp.sResources.getString(R.string.removerecord) + "\n" + DateUtil.dateAndTimeString(tempBasal.date)); - builder.setPositiveButton(MainApp.sResources.getString(R.string.ok), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - final String _id = tempBasal._id; - if (NSUpload.isIdValid(_id)) { - NSUpload.removeCareportalEntryFromNS(_id); - } else { - UploadQueue.removeID("dbAdd", _id); - } - MainApp.getDbHelper().delete(tempBasal); - Answers.getInstance().logCustom(new CustomEvent("RemoveTempBasal")); + builder.setTitle(MainApp.gs(R.string.confirmation)); + builder.setMessage(MainApp.gs(R.string.removerecord) + "\n" + DateUtil.dateAndTimeString(tempBasal.date)); + builder.setPositiveButton(MainApp.gs(R.string.ok), (dialog, id) -> { + final String _id = tempBasal._id; + if (NSUpload.isIdValid(_id)) { + NSUpload.removeCareportalEntryFromNS(_id); + } else { + UploadQueue.removeID("dbAdd", _id); } + MainApp.getDbHelper().delete(tempBasal); + FabricPrivacy.getInstance().logCustom(new CustomEvent("RemoveTempBasal")); }); - builder.setNegativeButton(MainApp.sResources.getString(R.string.cancel), null); + builder.setNegativeButton(MainApp.gs(R.string.cancel), null); builder.show(); break; } @@ -184,7 +185,7 @@ public class TreatmentsTemporaryBasalsFragment extends SubscriberFragment { } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.treatments_tempbasals_fragment, container, false); @@ -193,7 +194,7 @@ public class TreatmentsTemporaryBasalsFragment extends SubscriberFragment { llm = new LinearLayoutManager(view.getContext()); recyclerView.setLayoutManager(llm); - RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainApp.getConfigBuilder().getTemporaryBasalsFromHistory()); + RecyclerViewAdapter adapter = new RecyclerViewAdapter(TreatmentsPlugin.getPlugin().getTemporaryBasalsFromHistory()); recyclerView.setAdapter(adapter); tempBasalTotalView = (TextView) view.findViewById(R.id.tempbasals_totaltempiob); @@ -205,12 +206,12 @@ public class TreatmentsTemporaryBasalsFragment extends SubscriberFragment { } @Subscribe - public void onStatusEvent(final EventTempBasalChange ev) { + public void onStatusEvent(final EventTempBasalChange ignored) { updateGUI(); } @Subscribe - public void onStatusEvent(final EventNewBG ev) { + public void onStatusEvent(final EventNewBG ignored) { updateGUI(); } @@ -218,15 +219,11 @@ public class TreatmentsTemporaryBasalsFragment extends SubscriberFragment { protected void updateGUI() { Activity activity = getActivity(); if (activity != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - recyclerView.swapAdapter(new RecyclerViewAdapter(MainApp.getConfigBuilder().getTemporaryBasalsFromHistory()), false); - if (MainApp.getConfigBuilder().getLastCalculationTempBasals() != null) { - String totalText = DecimalFormatter.to2Decimal(MainApp.getConfigBuilder().getLastCalculationTempBasals().basaliob) + " U"; - tempBasalTotalView.setText(totalText); - } - } + activity.runOnUiThread(() -> { + recyclerView.swapAdapter(new RecyclerViewAdapter(TreatmentsPlugin.getPlugin().getTemporaryBasalsFromHistory()), false); + IobTotal tempBasalsCalculation = TreatmentsPlugin.getPlugin().getLastCalculationTempBasals(); + if (tempBasalsCalculation != null) + tempBasalTotalView.setText(DecimalFormatter.to2Decimal(tempBasalsCalculation.basaliob, " U")); }); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Wear/ActionStringHandler.java b/app/src/main/java/info/nightscout/androidaps/plugins/Wear/ActionStringHandler.java index da276af3c2..fdc14f32d4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Wear/ActionStringHandler.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Wear/ActionStringHandler.java @@ -19,14 +19,14 @@ import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.BgReading; -import info.nightscout.androidaps.db.DanaRHistoryRecord; import info.nightscout.androidaps.db.DatabaseHelper; import info.nightscout.androidaps.db.ProfileSwitch; import info.nightscout.androidaps.db.Source; +import info.nightscout.androidaps.db.TDD; import info.nightscout.androidaps.db.TempTarget; import info.nightscout.androidaps.interfaces.APSInterface; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.ProfileInterface; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.Actions.dialogs.FillDialog; import info.nightscout.androidaps.plugins.Careportal.Dialogs.NewNSTreatmentDialog; @@ -36,14 +36,16 @@ import info.nightscout.androidaps.plugins.Loop.LoopPlugin; import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPlugin; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.RecordTypes; import info.nightscout.androidaps.plugins.PumpDanaRKorean.DanaRKoreanPlugin; import info.nightscout.androidaps.plugins.PumpDanaRS.DanaRSPlugin; import info.nightscout.androidaps.plugins.PumpDanaRv2.DanaRv2Plugin; +import info.nightscout.androidaps.plugins.PumpInsight.InsightPlugin; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.androidaps.queue.Callback; import info.nightscout.utils.BolusWizard; import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.HardLimits; import info.nightscout.utils.SP; import info.nightscout.utils.SafeParse; import info.nightscout.utils.ToastUtils; @@ -92,7 +94,7 @@ public class ActionStringHandler { } else { return; } - Double insulinAfterConstraints = MainApp.getConfigBuilder().applyBolusConstraints(amount); + Double insulinAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(amount)).value(); rMessage += MainApp.instance().getString(R.string.primefill) + ": " + insulinAfterConstraints + "U"; if (insulinAfterConstraints - amount != 0) rMessage += "\n" + MainApp.instance().getString(R.string.constraintapllied); @@ -103,7 +105,7 @@ public class ActionStringHandler { ////////////////////////////////////////////// PRIME/FILL double amount = SafeParse.stringToDouble(act[1]); - Double insulinAfterConstraints = MainApp.getConfigBuilder().applyBolusConstraints(amount); + Double insulinAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(amount)).value(); rMessage += MainApp.instance().getString(R.string.primefill) + ": " + insulinAfterConstraints + "U"; if (insulinAfterConstraints - amount != 0) rMessage += "\n" + MainApp.instance().getString(R.string.constraintapllied); @@ -114,8 +116,8 @@ public class ActionStringHandler { ////////////////////////////////////////////// BOLUS double insulin = SafeParse.stringToDouble(act[1]); int carbs = SafeParse.stringToInt(act[2]); - Double insulinAfterConstraints = MainApp.getConfigBuilder().applyBolusConstraints(insulin); - Integer carbsAfterConstraints = MainApp.getConfigBuilder().applyCarbsConstraints(carbs); + Double insulinAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(insulin)).value(); + Integer carbsAfterConstraints = MainApp.getConstraintChecker().applyCarbsConstraints(new Constraint<>(carbs)).value(); rMessage += MainApp.instance().getString(R.string.bolus) + ": " + insulinAfterConstraints + "U\n"; rMessage += MainApp.instance().getString(R.string.carbs) + ": " + carbsAfterConstraints + "g"; @@ -149,11 +151,11 @@ public class ActionStringHandler { low *= Constants.MMOLL_TO_MGDL; high *= Constants.MMOLL_TO_MGDL; } - if (low < Constants.VERY_HARD_LIMIT_TEMP_MIN_BG[0] || low > Constants.VERY_HARD_LIMIT_TEMP_MIN_BG[1]) { + if (low < HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0] || low > HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1]) { sendError("Min-BG out of range!"); return; } - if (high < Constants.VERY_HARD_LIMIT_TEMP_MAX_BG[0] || high > Constants.VERY_HARD_LIMIT_TEMP_MAX_BG[1]) { + if (high < HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0] || high > HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1]) { sendError("Max-BG out of range!"); return; } @@ -179,7 +181,7 @@ public class ActionStringHandler { } else if ("wizard".equals(act[0])) { ////////////////////////////////////////////// WIZARD Integer carbsBeforeConstraints = SafeParse.stringToInt(act[1]); - Integer carbsAfterConstraints = MainApp.getConfigBuilder().applyCarbsConstraints(carbsBeforeConstraints); + Integer carbsAfterConstraints = MainApp.getConstraintChecker().applyCarbsConstraints(new Constraint<>(carbsBeforeConstraints)).value(); if (carbsAfterConstraints - carbsBeforeConstraints != 0) { sendError("Carb constraint violation!"); @@ -207,7 +209,7 @@ public class ActionStringHandler { BolusWizard bolusWizard = new BolusWizard(); bolusWizard.doCalc(profile, null, carbsAfterConstraints, 0d, useBG ? bgReading.valueToUnits(profile.getUnits()) : 0d, 0d, percentage, useBolusIOB, useBasalIOB, false, false); - Double insulinAfterConstraints = MainApp.getConfigBuilder().applyBolusConstraints(bolusWizard.calculatedTotalInsulin); + Double insulinAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(bolusWizard.calculatedTotalInsulin)).value(); if (insulinAfterConstraints - bolusWizard.calculatedTotalInsulin != 0) { sendError("Insulin contraint violation!" + "\nCannot deliver " + format.format(bolusWizard.calculatedTotalInsulin) + "!"); @@ -242,7 +244,7 @@ public class ActionStringHandler { lastBolusWizard = bolusWizard; } else if ("opencpp".equals(act[0])) { - ProfileSwitch activeProfileSwitch = MainApp.getConfigBuilder().getProfileSwitchFromHistory(System.currentTimeMillis()); + ProfileSwitch activeProfileSwitch = TreatmentsPlugin.getPlugin().getProfileSwitchFromHistory(System.currentTimeMillis()); if (activeProfileSwitch == null) { sendError("No active profile switch!"); return; @@ -254,7 +256,7 @@ public class ActionStringHandler { } } else if ("cppset".equals(act[0])) { - ProfileSwitch activeProfileSwitch = MainApp.getConfigBuilder().getProfileSwitchFromHistory(System.currentTimeMillis()); + ProfileSwitch activeProfileSwitch = TreatmentsPlugin.getPlugin().getProfileSwitchFromHistory(System.currentTimeMillis()); if (activeProfileSwitch == null) { sendError("No active profile switch!"); return; @@ -268,23 +270,10 @@ public class ActionStringHandler { } else if ("tddstats".equals(act[0])) { Object activePump = MainApp.getConfigBuilder().getActivePump(); - PumpInterface dana = MainApp.getSpecificPlugin(DanaRPlugin.class); - PumpInterface danaRS = MainApp.getSpecificPlugin(DanaRSPlugin.class); - PumpInterface danaV2 = MainApp.getSpecificPlugin(DanaRv2Plugin.class); - PumpInterface danaKorean = MainApp.getSpecificPlugin(DanaRKoreanPlugin.class); - - - if ((dana == null || dana != activePump) && - (danaV2 == null || danaV2 != activePump) && - (danaKorean == null || danaKorean != activePump) && - (danaRS == null || danaRS != activePump) - ) { - sendError("Pump does not support TDDs!"); - return; - } else { + if (activePump != null) { // check if DB up to date - List dummies = new LinkedList(); - List historyList = getTDDList(dummies); + List dummies = new LinkedList(); + List historyList = getTDDList(dummies); if (isOldData(historyList)) { rTitle = "TDD"; @@ -298,18 +287,18 @@ public class ActionStringHandler { } else { rMessage += "trying to fetch data from pump."; - ConfigBuilderPlugin.getCommandQueue().loadHistory(RecordTypes.RECORD_TYPE_DAILY, new Callback() { + ConfigBuilderPlugin.getCommandQueue().loadTDDs(new Callback() { @Override public void run() { - List dummies = new LinkedList(); - List historyList = getTDDList(dummies); + List dummies = new LinkedList(); + List historyList = getTDDList(dummies); if (isOldData(historyList)) { - sendStatusmessage("TDD", "TDD: Still old data! Cannot load from pump."); + sendStatusmessage("TDD", "TDD: Still old data! Cannot load from pump.\n" + generateTDDMessage(historyList, dummies)); } else { sendStatusmessage("TDD", generateTDDMessage(historyList, dummies)); } - } - }); + } + }); } } else { // if up to date: prepare, send (check if CPP is activated -> add CPP stats) @@ -328,18 +317,34 @@ public class ActionStringHandler { lastConfirmActionString = rAction; } - private static String generateTDDMessage(List historyList, List dummies) { + private static String generateTDDMessage(List historyList, List dummies) { - ProfileInterface activeProfile = MainApp.getConfigBuilder().getActiveProfileInterface(); + Profile profile = MainApp.getConfigBuilder().getProfile(); - if (activeProfile == null) { + if (profile == null) { return "No profile loaded :("; } + if (historyList.isEmpty()) { + return "No history data!"; + } + DateFormat df = new SimpleDateFormat("dd.MM."); String message = ""; - double refTDD = activeProfile.getProfile().getDefaultProfile().baseBasalSum() * 2; + double refTDD = profile.baseBasalSum() * 2; + + PumpInterface pump = MainApp.getConfigBuilder().getActivePump(); + if (df.format(new Date(historyList.get(0).date)).equals(df.format(new Date()))) { + double tdd = historyList.get(0).getTotal(); + historyList.remove(0); + message += "Today: " + DecimalFormatter.to2Decimal(tdd) + "U " + (DecimalFormatter.to0Decimal(100 * tdd / refTDD) + "%") + "\n"; + message += "\n"; + } else if (pump != null && pump instanceof DanaRPlugin) { + double tdd = DanaRPump.getInstance().dailyTotalUnits; + message += "Today: " + DecimalFormatter.to2Decimal(tdd) + "U " + (DecimalFormatter.to0Decimal(100 * tdd / refTDD) + "%") + "\n"; + message += "\n"; + } int i = 0; double sum = 0d; @@ -348,8 +353,8 @@ public class ActionStringHandler { double weighted07 = 0d; Collections.reverse(historyList); - for (DanaRHistoryRecord record : historyList) { - double tdd = record.recordDailyBolus + record.recordDailyBasal; + for (TDD record : historyList) { + double tdd = record.getTotal(); if (i == 0) { weighted03 = tdd; weighted05 = tdd; @@ -367,59 +372,63 @@ public class ActionStringHandler { message += "0.5: " + DecimalFormatter.to2Decimal(weighted05) + "U " + (DecimalFormatter.to0Decimal(100 * weighted05 / refTDD) + "%") + "\n"; message += "0.7: " + DecimalFormatter.to2Decimal(weighted07) + "U " + (DecimalFormatter.to0Decimal(100 * weighted07 / refTDD) + "%") + "\n"; message += "\n"; - - PumpInterface pump = MainApp.getConfigBuilder().getActivePump(); - if (pump != null && pump instanceof DanaRPlugin) { - double tdd = DanaRPump.getInstance().dailyTotalUnits; - message += "Today: " + DecimalFormatter.to2Decimal(tdd) + "U " + (DecimalFormatter.to0Decimal(100 * tdd / refTDD) + "%") + "\n"; - message += "\n"; - } + Collections.reverse(historyList); //add TDDs: - Collections.reverse(historyList); - for (DanaRHistoryRecord record : historyList) { - double tdd = record.recordDailyBolus + record.recordDailyBasal; - message += df.format(new Date(record.recordDate)) + " " + DecimalFormatter.to2Decimal(tdd) + "U " + (DecimalFormatter.to0Decimal(100 * tdd / refTDD) + "%") + (dummies.contains(record) ? "x" : "") + "\n"; + for (TDD record : historyList) { + double tdd = record.getTotal(); + message += df.format(new Date(record.date)) + " " + DecimalFormatter.to2Decimal(tdd) + "U " + (DecimalFormatter.to0Decimal(100 * tdd / refTDD) + "%") + (dummies.contains(record) ? "x" : "") + "\n"; } return message; } - public static boolean isOldData(List historyList) { + public static boolean isOldData(List historyList) { + Object activePump = MainApp.getConfigBuilder().getActivePump(); + PumpInterface dana = MainApp.getSpecificPlugin(DanaRPlugin.class); + PumpInterface danaRS = MainApp.getSpecificPlugin(DanaRSPlugin.class); + PumpInterface danaV2 = MainApp.getSpecificPlugin(DanaRv2Plugin.class); + PumpInterface danaKorean = MainApp.getSpecificPlugin(DanaRKoreanPlugin.class); + PumpInterface insight = MainApp.getSpecificPlugin(InsightPlugin.class); + + boolean startsYesterday = activePump == dana || activePump == danaRS || activePump == danaV2 || activePump == danaKorean || activePump == insight; + DateFormat df = new SimpleDateFormat("dd.MM."); - return (historyList.size() < 3 || !(df.format(new Date(historyList.get(0).recordDate)).equals(df.format(new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24))))); + return (historyList.size() < 3 || !(df.format(new Date(historyList.get(0).date)).equals(df.format(new Date(System.currentTimeMillis() - (startsYesterday ? 1000 * 60 * 60 * 24 : 0)))))); } @NonNull - public static List getTDDList(List returnDummies) { - List historyList = MainApp.getDbHelper().getDanaRHistoryRecordsByType(RecordTypes.RECORD_TYPE_DAILY); + public static List getTDDList(List returnDummies) { + List historyList = MainApp.getDbHelper().getTDDs(); - //only use newest 10 historyList = historyList.subList(0, Math.min(10, historyList.size())); - //fill single gaps - List dummies = (returnDummies != null) ? returnDummies : (new LinkedList()); + //fill single gaps - only needed for Dana*R data + List dummies = (returnDummies != null) ? returnDummies : (new LinkedList()); DateFormat df = new SimpleDateFormat("dd.MM."); for (int i = 0; i < historyList.size() - 1; i++) { - DanaRHistoryRecord elem1 = historyList.get(i); - DanaRHistoryRecord elem2 = historyList.get(i + 1); + TDD elem1 = historyList.get(i); + TDD elem2 = historyList.get(i + 1); - if (!df.format(new Date(elem1.recordDate)).equals(df.format(new Date(elem2.recordDate + 25 * 60 * 60 * 1000)))) { - DanaRHistoryRecord dummy = new DanaRHistoryRecord(); - dummy.recordDate = elem1.recordDate - 24 * 60 * 60 * 1000; - dummy.recordDailyBasal = elem1.recordDailyBasal / 2; - dummy.recordDailyBolus = elem1.recordDailyBolus / 2; + if (!df.format(new Date(elem1.date)).equals(df.format(new Date(elem2.date + 25 * 60 * 60 * 1000)))) { + TDD dummy = new TDD(); + dummy.date = elem1.date - 24 * 60 * 60 * 1000; + dummy.basal = elem1.basal / 2; + dummy.bolus = elem1.bolus / 2; dummies.add(dummy); - elem1.recordDailyBasal /= 2; - elem1.recordDailyBolus /= 2; + elem1.basal /= 2; + elem1.bolus /= 2; + + } } historyList.addAll(dummies); - Collections.sort(historyList, new Comparator() { + Collections.sort(historyList, new Comparator() { @Override - public int compare(DanaRHistoryRecord lhs, DanaRHistoryRecord rhs) { - return (int) (rhs.recordDate - lhs.recordDate); + public int compare(TDD lhs, TDD rhs) { + return (int) (rhs.date - lhs.date); } }); + return historyList; } @@ -432,21 +441,21 @@ public class ActionStringHandler { private static String getLoopStatus() { String ret = ""; // decide if enabled/disabled closed/open; what Plugin as APS? - final LoopPlugin activeloop = MainApp.getConfigBuilder().getActiveLoop(); - if (activeloop != null && activeloop.isEnabled(activeloop.getType())) { - if (MainApp.getConfigBuilder().isClosedModeEnabled()) { + final LoopPlugin loopPlugin = LoopPlugin.getPlugin(); + if (loopPlugin.isEnabled(loopPlugin.getType())) { + if (MainApp.getConstraintChecker().isClosedLoopAllowed().value()) { ret += "CLOSED LOOP\n"; } else { ret += "OPEN LOOP\n"; } - final APSInterface aps = MainApp.getConfigBuilder().getActiveAPS(); + final APSInterface aps = ConfigBuilderPlugin.getActiveAPS(); ret += "APS: " + ((aps == null) ? "NO APS SELECTED!" : ((PluginBase) aps).getName()); - if (activeloop.lastRun != null) { - if (activeloop.lastRun.lastAPSRun != null) - ret += "\nLast Run: " + DateUtil.timeString(activeloop.lastRun.lastAPSRun); + if (LoopPlugin.lastRun != null) { + if (LoopPlugin.lastRun.lastAPSRun != null) + ret += "\nLast Run: " + DateUtil.timeString(LoopPlugin.lastRun.lastAPSRun); - if (activeloop.lastRun.lastEnact != null) - ret += "\nLast Enact: " + DateUtil.timeString(activeloop.lastRun.lastEnact); + if (LoopPlugin.lastRun.lastEnact != null) + ret += "\nLast Enact: " + DateUtil.timeString(LoopPlugin.lastRun.lastEnact); } @@ -470,7 +479,7 @@ public class ActionStringHandler { } //Check for Temp-Target: - TempTarget tempTarget = MainApp.getConfigBuilder().getTempTargetFromHistory(); + TempTarget tempTarget = TreatmentsPlugin.getPlugin().getTempTargetFromHistory(); if (tempTarget != null) { ret += "Temp Target: " + Profile.toTargetRangeString(tempTarget.low, tempTarget.low, Constants.MGDL, profile.getUnits()); ret += "\nuntil: " + DateUtil.timeString(tempTarget.originalEnd()); @@ -479,7 +488,7 @@ public class ActionStringHandler { ret += "DEFAULT RANGE: "; ret += profile.getTargetLow() + " - " + profile.getTargetHigh(); - ret += " target: " + (profile.getTargetLow() + profile.getTargetHigh()) / 2; + ret += " target: " + profile.getTarget(); return ret; } @@ -493,7 +502,7 @@ public class ActionStringHandler { return "No profile set :("; } - APSInterface usedAPS = MainApp.getConfigBuilder().getActiveAPS(); + APSInterface usedAPS = ConfigBuilderPlugin.getActiveAPS(); if (usedAPS == null) { return "No active APS :(!"; } @@ -503,7 +512,7 @@ public class ActionStringHandler { return "Last result not available!"; } - if (!result.changeRequested) { + if (!result.isChangeRequested()) { ret += MainApp.sResources.getString(R.string.nochangerequested) + "\n"; } else if (result.rate == 0 && result.duration == 0) { ret += MainApp.sResources.getString(R.string.canceltemp) + "\n"; @@ -534,7 +543,7 @@ public class ActionStringHandler { if ("fill".equals(act[0])) { Double amount = SafeParse.stringToDouble(act[1]); - Double insulinAfterConstraints = MainApp.getConfigBuilder().applyBolusConstraints(amount); + Double insulinAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(amount)).value(); if (amount - insulinAfterConstraints != 0) { ToastUtils.showToastInUiThread(MainApp.instance(), "aborting: previously applied constraint changed"); sendError("aborting: previously applied constraint changed"); @@ -577,18 +586,18 @@ public class ActionStringHandler { //check for validity if (percentage < Constants.CPP_MIN_PERCENTAGE || percentage > Constants.CPP_MAX_PERCENTAGE) { - msg += String.format(MainApp.sResources.getString(R.string.openapsma_valueoutofrange), "Profile-Percentage") + "\n"; + msg += String.format(MainApp.sResources.getString(R.string.valueoutofrange), "Profile-Percentage") + "\n"; } if (timeshift < 0 || timeshift > 23) { - msg += String.format(MainApp.sResources.getString(R.string.openapsma_valueoutofrange), "Profile-Timeshift") + "\n"; + msg += String.format(MainApp.sResources.getString(R.string.valueoutofrange), "Profile-Timeshift") + "\n"; } final Profile profile = MainApp.getConfigBuilder().getProfile(); - if (profile == null || profile.getBasal() == null) { - msg += MainApp.sResources.getString(R.string.cpp_notloadedplugins) + "\n"; + if (profile == null) { + msg += MainApp.sResources.getString(R.string.notloadedplugins) + "\n"; } if (!"".equals(msg)) { - msg += MainApp.sResources.getString(R.string.cpp_valuesnotstored); + msg += MainApp.sResources.getString(R.string.valuesnotstored); String rTitle = "STATUS"; String rAction = "statusmessage"; WearPlugin.getPlugin().requestActionConfirmation(rTitle, msg, rAction); @@ -603,22 +612,17 @@ public class ActionStringHandler { } private static void generateTempTarget(int duration, double low, double high) { - TempTarget tempTarget = new TempTarget(); - tempTarget.date = System.currentTimeMillis(); - tempTarget.durationInMinutes = duration; - tempTarget.reason = "WearPlugin"; - tempTarget.source = Source.USER; + TempTarget tempTarget = new TempTarget() + .date(System.currentTimeMillis()) + .duration(duration) + .reason("WearPlugin") + .source(Source.USER); if (tempTarget.durationInMinutes != 0) { - tempTarget.low = low; - tempTarget.high = high; + tempTarget.low(low).high(high); } else { - tempTarget.low = 0; - tempTarget.high = 0; + tempTarget.low(0).high(0); } - MainApp.getDbHelper().createOrUpdate(tempTarget); - - //TODO: Nightscout-Treatment for Temp-Target! - //ConfigBuilderPlugin.uploadCareportalEntryToNS(data); + TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget); } private static void doFillBolus(final Double amount) { @@ -643,16 +647,20 @@ public class ActionStringHandler { detailedBolusInfo.insulin = amount; detailedBolusInfo.carbs = carbs; detailedBolusInfo.source = Source.USER; - ConfigBuilderPlugin.getCommandQueue().bolus(detailedBolusInfo, new Callback() { - @Override - public void run() { - if (!result.success) { - sendError(MainApp.sResources.getString(R.string.treatmentdeliveryerror) + - "\n" + - result.comment); + if (detailedBolusInfo.insulin > 0 || ConfigBuilderPlugin.getActivePump().getPumpDescription().storesCarbInfo) { + ConfigBuilderPlugin.getCommandQueue().bolus(detailedBolusInfo, new Callback() { + @Override + public void run() { + if (!result.success) { + sendError(MainApp.sResources.getString(R.string.treatmentdeliveryerror) + + "\n" + + result.comment); + } } - } - }); + }); + } else { + TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo); + } } private synchronized static void sendError(String errormessage) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Wear/WearPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Wear/WearPlugin.java index 3316d68d12..dcda0bc0aa 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Wear/WearPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Wear/WearPlugin.java @@ -5,7 +5,6 @@ import android.content.Intent; import com.squareup.otto.Subscribe; -import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.events.EventBolusRequested; @@ -17,7 +16,10 @@ import info.nightscout.androidaps.events.EventRefreshOverview; import info.nightscout.androidaps.events.EventTempBasalChange; import info.nightscout.androidaps.events.EventTreatmentChange; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.plugins.Loop.LoopPlugin; +import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateGui; import info.nightscout.androidaps.plugins.Overview.events.EventDismissBolusprogressIfRunning; import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress; import info.nightscout.androidaps.plugins.Wear.wearintegration.WatchUpdaterService; @@ -27,10 +29,8 @@ import info.nightscout.utils.SP; * Created by adrian on 17/11/16. */ -public class WearPlugin implements PluginBase { +public class WearPlugin extends PluginBase { - private static boolean fragmentEnabled = true; - private boolean fragmentVisible = true; private static WatchUpdaterService watchUS; private final Context ctx; @@ -39,6 +39,7 @@ public class WearPlugin implements PluginBase { public static WearPlugin getPlugin() { return wearPlugin; } + public static WearPlugin initPlugin(Context ctx) { if (wearPlugin == null) { @@ -49,79 +50,28 @@ public class WearPlugin implements PluginBase { } WearPlugin(Context ctx) { + super(new PluginDescription() + .mainType(PluginType.GENERAL) + .fragmentClass(WearFragment.class.getName()) + .pluginName(R.string.wear) + .shortName(R.string.wear_shortname) + .preferencesId(R.xml.pref_wear) + ); this.ctx = ctx; + } + + @Override + protected void onStart() { MainApp.bus().register(this); - } - - @Override - public int getType() { - return PluginBase.GENERAL; - } - - @Override - public String getFragmentClass() { - return WearFragment.class.getName(); - } - - @Override - public String getName() { - return ctx.getString(R.string.wear); - } - - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.wear_shortname); - if (!name.trim().isEmpty()){ - //only if translation exists - return name; + if (watchUS != null) { + watchUS.setSettings(); } - // use long name as fallback - return getName(); + super.onStart(); } @Override - public boolean isEnabled(int type) { - return type == GENERAL && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return type == GENERAL && fragmentVisible; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return true; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == GENERAL) { - this.fragmentEnabled = fragmentEnabled; - if (watchUS != null) { - watchUS.setSettings(); - } - } - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == GENERAL) this.fragmentVisible = fragmentVisible; - } - - @Override - public int getPreferencesId() { - return R.xml.pref_wear; + protected void onStop() { + MainApp.bus().unregister(this); } private void sendDataToWatch(boolean status, boolean basals, boolean bgValue) { @@ -168,6 +118,11 @@ public class WearPlugin implements PluginBase { sendDataToWatch(true, true, false); } + @Subscribe + public void onStatusEvent(final EventOpenAPSUpdateGui ev) { + sendDataToWatch(true, true, false); + } + @Subscribe public void onStatusEvent(final EventExtendedBolusChange ev) { sendDataToWatch(true, true, false); @@ -185,11 +140,7 @@ public class WearPlugin implements PluginBase { @Subscribe public void onStatusEvent(final EventRefreshOverview ev) { - - LoopPlugin activeloop = MainApp.getConfigBuilder().getActiveLoop(); - if (activeloop == null) return; - - if(WatchUpdaterService.shouldReportLoopStatus(activeloop.isEnabled(PluginBase.LOOP))) { + if (WatchUpdaterService.shouldReportLoopStatus(LoopPlugin.getPlugin().isEnabled(PluginType.LOOP))) { sendDataToWatch(true, false, false); } } @@ -197,10 +148,12 @@ public class WearPlugin implements PluginBase { @Subscribe public void onStatusEvent(final EventOverviewBolusProgress ev) { - Intent intent = new Intent(ctx, WatchUpdaterService.class).setAction(WatchUpdaterService.ACTION_SEND_BOLUSPROGRESS); - intent.putExtra("progresspercent", ev.percent); - intent.putExtra("progressstatus", ev.status); - ctx.startService(intent); + if (!ev.isSMB() || SP.getBoolean("wear_notifySMB", true)) { + Intent intent = new Intent(ctx, WatchUpdaterService.class).setAction(WatchUpdaterService.ACTION_SEND_BOLUSPROGRESS); + intent.putExtra("progresspercent", ev.percent); + intent.putExtra("progressstatus", ev.status); + ctx.startService(intent); + } } @Subscribe @@ -215,10 +168,10 @@ public class WearPlugin implements PluginBase { @Subscribe public void onStatusEvent(final EventDismissBolusprogressIfRunning ev) { - if(ev.result == null) return; + if (ev.result == null) return; String status; - if(ev.result.success){ + if (ev.result.success) { status = MainApp.sResources.getString(R.string.success); } else { status = MainApp.sResources.getString(R.string.nosuccess); @@ -229,7 +182,7 @@ public class WearPlugin implements PluginBase { ctx.startService(intent); } - public void requestActionConfirmation(String title, String message, String actionstring){ + public void requestActionConfirmation(String title, String message, String actionstring) { Intent intent = new Intent(ctx, WatchUpdaterService.class).setAction(WatchUpdaterService.ACTION_SEND_ACTIONCONFIRMATIONREQUEST); intent.putExtra("title", title); @@ -238,10 +191,6 @@ public class WearPlugin implements PluginBase { ctx.startService(intent); } - public static boolean isEnabled() { - return fragmentEnabled; - } - public static void registerWatchUpdaterService(WatchUpdaterService wus) { watchUS = wus; } @@ -249,11 +198,4 @@ public class WearPlugin implements PluginBase { public static void unRegisterWatchUpdaterService() { watchUS = null; } - - public void overviewNotification(int id, String message) { - if(SP.getBoolean("wear_overview_notification", false)){ - ActionStringHandler.expectNotificationAction(message, id); - } - } - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Wear/wearintegration/WatchUpdaterService.java b/app/src/main/java/info/nightscout/androidaps/plugins/Wear/wearintegration/WatchUpdaterService.java index d7bda4781b..0d809a7bab 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Wear/wearintegration/WatchUpdaterService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Wear/wearintegration/WatchUpdaterService.java @@ -6,6 +6,8 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.os.BatteryManager; import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.util.Log; @@ -35,14 +37,17 @@ import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.DatabaseHelper; import info.nightscout.androidaps.db.TemporaryBasal; -import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.plugins.IobCobCalculator.CobInfo; +import info.nightscout.androidaps.plugins.Treatments.Treatment; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.TreatmentsInterface; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensData; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; -import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Loop.LoopPlugin; import info.nightscout.androidaps.plugins.NSClientInternal.data.NSDeviceStatus; import info.nightscout.androidaps.plugins.Overview.OverviewPlugin; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.androidaps.plugins.Wear.ActionStringHandler; import info.nightscout.androidaps.plugins.Wear.WearPlugin; import info.nightscout.utils.DecimalFormatter; @@ -82,6 +87,7 @@ public class WatchUpdaterService extends WearableListenerService implements private static Logger log = LoggerFactory.getLogger(WatchUpdaterService.class); + private Handler handler; @Override public void onCreate() { @@ -91,6 +97,11 @@ public class WatchUpdaterService extends WearableListenerService implements if (wear_integration) { googleApiConnect(); } + if (handler == null) { + HandlerThread handlerThread = new HandlerThread(this.getClass().getSimpleName() + "Handler"); + handlerThread.start(); + handler = new Handler(handlerThread.getLooper()); + } } public void listenForChangeInSettings() { @@ -98,7 +109,7 @@ public class WatchUpdaterService extends WearableListenerService implements } public void setSettings() { - wear_integration = WearPlugin.isEnabled(); + wear_integration = WearPlugin.getPlugin().isEnabled(PluginType.GENERAL); if (wear_integration) { googleApiConnect(); } @@ -123,34 +134,33 @@ public class WatchUpdaterService extends WearableListenerService implements @Override public int onStartCommand(Intent intent, int flags, int startId) { - String action = null; - if (intent != null) { - action = intent.getAction(); - } + String action = intent != null ? intent.getAction() : null; if (wear_integration) { - if (googleApiClient.isConnected()) { - if (ACTION_RESEND.equals(action)) { - resendData(); - } else if (ACTION_OPEN_SETTINGS.equals(action)) { - sendNotification(); - } else if (ACTION_SEND_STATUS.equals(action)) { - sendStatus(); - } else if (ACTION_SEND_BASALS.equals(action)) { - sendBasals(); - } else if (ACTION_SEND_BOLUSPROGRESS.equals(action)) { - sendBolusProgress(intent.getIntExtra("progresspercent", 0), intent.hasExtra("progressstatus") ? intent.getStringExtra("progressstatus") : ""); - } else if (ACTION_SEND_ACTIONCONFIRMATIONREQUEST.equals(action)) { - String title = intent.getStringExtra("title"); - String message = intent.getStringExtra("message"); - String actionstring = intent.getStringExtra("actionstring"); - sendActionConfirmationRequest(title, message, actionstring); + handler.post(() -> { + if (googleApiClient.isConnected()) { + if (ACTION_RESEND.equals(action)) { + resendData(); + } else if (ACTION_OPEN_SETTINGS.equals(action)) { + sendNotification(); + } else if (ACTION_SEND_STATUS.equals(action)) { + sendStatus(); + } else if (ACTION_SEND_BASALS.equals(action)) { + sendBasals(); + } else if (ACTION_SEND_BOLUSPROGRESS.equals(action)) { + sendBolusProgress(intent.getIntExtra("progresspercent", 0), intent.hasExtra("progressstatus") ? intent.getStringExtra("progressstatus") : ""); + } else if (ACTION_SEND_ACTIONCONFIRMATIONREQUEST.equals(action)) { + String title = intent.getStringExtra("title"); + String message = intent.getStringExtra("message"); + String actionstring = intent.getStringExtra("actionstring"); + sendActionConfirmationRequest(title, message, actionstring); + } else { + sendData(); + } } else { - sendData(); + googleApiClient.connect(); } - } else { - googleApiClient.connect(); - } + }); } return START_STICKY; @@ -278,7 +288,7 @@ public class WatchUpdaterService extends WearableListenerService implements deltastring += DecimalFormatter.to0Decimal(Math.abs(deltaMGDL)); } } else { - if (detailed){ + if (detailed) { deltastring += DecimalFormatter.to2Decimal(Math.abs(deltaMMOL)); } else { deltastring += DecimalFormatter.to1Decimal(Math.abs(deltaMMOL)); @@ -345,11 +355,13 @@ public class WatchUpdaterService extends WearableListenerService implements } long now = System.currentTimeMillis(); - long startTimeWindow = now - (long) (60000 * 60 * 5.5); + final long startTimeWindow = now - (long) (60000 * 60 * 5.5); ArrayList basals = new ArrayList<>(); ArrayList temps = new ArrayList<>(); + ArrayList boluses = new ArrayList<>(); + ArrayList predictions = new ArrayList<>(); Profile profile = MainApp.getConfigBuilder().getProfile(); @@ -364,21 +376,24 @@ public class WatchUpdaterService extends WearableListenerService implements double beginBasalValue = profile.getBasal(beginBasalSegmentTime); double endBasalValue = beginBasalValue; - TemporaryBasal tb1 = MainApp.getConfigBuilder().getTempBasalFromHistory(runningTime); - TemporaryBasal tb2 = MainApp.getConfigBuilder().getTempBasalFromHistory(runningTime); + TemporaryBasal tb1 = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(runningTime); + TemporaryBasal tb2 = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(runningTime); double tb_before = beginBasalValue; double tb_amount = beginBasalValue; long tb_start = runningTime; if (tb1 != null) { tb_before = beginBasalValue; - tb_amount = tb1.tempBasalConvertedToAbsolute(runningTime); - tb_start = runningTime; + Profile profileTB = MainApp.getConfigBuilder().getProfile(runningTime); + if (profileTB != null) { + tb_amount = tb1.tempBasalConvertedToAbsolute(runningTime, profileTB); + tb_start = runningTime; + } } for (; runningTime < now; runningTime += 5 * 60 * 1000) { - + Profile profileTB = MainApp.getConfigBuilder().getProfile(runningTime); //basal rate endBasalValue = profile.getBasal(runningTime); if (endBasalValue != beginBasalValue) { @@ -391,7 +406,7 @@ public class WatchUpdaterService extends WearableListenerService implements } //temps - tb2 = MainApp.getConfigBuilder().getTempBasalFromHistory(runningTime); + tb2 = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(runningTime); if (tb1 == null && tb2 == null) { //no temp stays no temp @@ -406,10 +421,10 @@ public class WatchUpdaterService extends WearableListenerService implements tb1 = tb2; tb_start = runningTime; tb_before = endBasalValue; - tb_amount = tb1.tempBasalConvertedToAbsolute(runningTime); + tb_amount = tb1.tempBasalConvertedToAbsolute(runningTime, profileTB); } else if (tb1 != null && tb2 != null) { - double currentAmount = tb2.tempBasalConvertedToAbsolute(runningTime); + double currentAmount = tb2.tempBasalConvertedToAbsolute(runningTime, profileTB); if (currentAmount != tb_amount) { temps.add(tempDatamap(tb_start, tb_before, runningTime, currentAmount, tb_amount)); tb_start = runningTime; @@ -424,13 +439,14 @@ public class WatchUpdaterService extends WearableListenerService implements basals.add(basalMap(beginBasalSegmentTime, runningTime, beginBasalValue)); } if (tb1 != null) { - tb2 = MainApp.getConfigBuilder().getTempBasalFromHistory(now); //use "now" to express current situation + tb2 = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(now); //use "now" to express current situation if (tb2 == null) { //express the cancelled temp by painting it down one minute early temps.add(tempDatamap(tb_start, tb_before, now - 1 * 60 * 1000, endBasalValue, tb_amount)); } else { //express currently running temp by painting it a bit into the future - double currentAmount = tb2.tempBasalConvertedToAbsolute(now); + Profile profileNow = MainApp.getConfigBuilder().getProfile(now); + double currentAmount = tb2.tempBasalConvertedToAbsolute(now, profileNow); if (currentAmount != tb_amount) { temps.add(tempDatamap(tb_start, tb_before, now, tb_amount, tb_amount)); temps.add(tempDatamap(now, tb_amount, runningTime + 5 * 60 * 1000, currentAmount, currentAmount)); @@ -439,17 +455,40 @@ public class WatchUpdaterService extends WearableListenerService implements } } } else { - tb2 = MainApp.getConfigBuilder().getTempBasalFromHistory(now); //use "now" to express current situation + tb2 = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(now); //use "now" to express current situation if (tb2 != null) { //onset at the end - double currentAmount = tb2.tempBasalConvertedToAbsolute(runningTime); + Profile profileTB = MainApp.getConfigBuilder().getProfile(runningTime); + double currentAmount = tb2.tempBasalConvertedToAbsolute(runningTime, profileTB); temps.add(tempDatamap(now - 1 * 60 * 1000, endBasalValue, runningTime + 5 * 60 * 1000, currentAmount, currentAmount)); } } + List treatments = TreatmentsPlugin.getPlugin().getTreatmentsFromHistory(); + for (Treatment treatment : treatments) { + if (treatment.date > startTimeWindow) { + boluses.add(treatmentMap(treatment.date, treatment.insulin, treatment.carbs, treatment.isSMB, treatment.isValid)); + } + + } + + final LoopPlugin.LastRun finalLastRun = LoopPlugin.lastRun; + if (SP.getBoolean("wear_predictions", true) && finalLastRun != null && finalLastRun.request.hasPredictions && finalLastRun.constraintsProcessed != null) { + List predArray = finalLastRun.constraintsProcessed.getPredictions(); + + if (!predArray.isEmpty()) { + for (BgReading bg : predArray) { + predictions.add(predictionMap(bg.date, bg.value)); + } + } + } + + DataMap dm = new DataMap(); dm.putDataMapArrayList("basals", basals); dm.putDataMapArrayList("temps", temps); + dm.putDataMapArrayList("boluses", boluses); + dm.putDataMapArrayList("predictions", predictions); new SendToDataLayerThread(BASAL_DATA_PATH, googleApiClient).execute(dm); } @@ -472,6 +511,23 @@ public class WatchUpdaterService extends WearableListenerService implements return dm; } + private DataMap treatmentMap(long date, double bolus, double carbs, boolean isSMB, boolean isValid) { + DataMap dm = new DataMap(); + dm.putLong("date", date); + dm.putDouble("bolus", bolus); + dm.putDouble("carbs", carbs); + dm.putBoolean("isSMB", isSMB); + dm.putBoolean("isValid", isValid); + return dm; + } + + private DataMap predictionMap(long timestamp, double sgv) { + DataMap dm = new DataMap(); + dm.putLong("timestamp", timestamp); + dm.putDouble("sgv", sgv); + return dm; + } + private void sendNotification() { if (googleApiClient.isConnected()) { @@ -523,27 +579,31 @@ public class WatchUpdaterService extends WearableListenerService implements private void sendStatus() { if (googleApiClient.isConnected()) { - - TreatmentsInterface treatmentsInterface = MainApp.getConfigBuilder(); - treatmentsInterface.updateTotalIOBTreatments(); - IobTotal bolusIob = treatmentsInterface.getLastCalculationTreatments().round(); - treatmentsInterface.updateTotalIOBTempBasals(); - IobTotal basalIob = treatmentsInterface.getLastCalculationTempBasals().round(); - - String iobSum = DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob); - String iobDetail = "(" + DecimalFormatter.to2Decimal(bolusIob.iob) + "|" + DecimalFormatter.to2Decimal(basalIob.basaliob) + ")"; - String cobString = generateCOBString(); - String currentBasal = generateBasalString(treatmentsInterface); - - //bgi - String bgiString = ""; Profile profile = MainApp.getConfigBuilder().getProfile(); - if(profile!=null) { + String status = MainApp.instance().getString(R.string.noprofile); + String iobSum, iobDetail, cobString, currentBasal, bgiString; + iobSum = iobDetail = cobString = currentBasal = bgiString = ""; + if (profile != null) { + TreatmentsInterface treatmentsInterface = TreatmentsPlugin.getPlugin(); + treatmentsInterface.updateTotalIOBTreatments(); + IobTotal bolusIob = treatmentsInterface.getLastCalculationTreatments().round(); + treatmentsInterface.updateTotalIOBTempBasals(); + IobTotal basalIob = treatmentsInterface.getLastCalculationTempBasals().round(); + + iobSum = DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob); + iobDetail = "(" + DecimalFormatter.to2Decimal(bolusIob.iob) + "|" + DecimalFormatter.to2Decimal(basalIob.basaliob) + ")"; + cobString = generateCOBString(); + currentBasal = generateBasalString(treatmentsInterface); + + //bgi + + double bgi = -(bolusIob.activity + basalIob.activity) * 5 * profile.getIsf(); bgiString = "" + ((bgi >= 0) ? "+" : "") + DecimalFormatter.to1Decimal(bgi); + + status = generateStatusString(profile, currentBasal, iobSum, iobDetail, bgiString); } - String status = generateStatusString(profile, currentBasal,iobSum, iobDetail, bgiString); //batteries int phoneBattery = getBatteryLevel(getApplicationContext()); @@ -552,9 +612,9 @@ public class WatchUpdaterService extends WearableListenerService implements long openApsStatus = -1; //OpenAPS status - if(Config.APS){ + if (Config.APS) { //we are AndroidAPS - openApsStatus = LoopPlugin.lastRun != null && LoopPlugin.lastRun.lastEnact != null && LoopPlugin.lastRun.lastEnact.getTime() != 0 ? LoopPlugin.lastRun.lastEnact.getTime(): -1; + openApsStatus = LoopPlugin.lastRun != null && LoopPlugin.lastRun.lastEnact != null && LoopPlugin.lastRun.lastEnact.getTime() != 0 ? LoopPlugin.lastRun.lastEnact.getTime() : -1; } else { //NSClient or remote openApsStatus = NSDeviceStatus.getOpenApsTimestamp(); @@ -607,12 +667,12 @@ public class WatchUpdaterService extends WearableListenerService implements return status; } - LoopPlugin activeloop = MainApp.getConfigBuilder().getActiveLoop(); + LoopPlugin activeloop = LoopPlugin.getPlugin(); - if (activeloop != null && !activeloop.isEnabled(PluginBase.LOOP)) { + if (!activeloop.isEnabled(PluginType.LOOP)) { status += getString(R.string.disabledloop) + "\n"; lastLoopStatus = false; - } else if (activeloop != null && activeloop.isEnabled(PluginBase.LOOP)) { + } else { lastLoopStatus = true; } @@ -659,9 +719,13 @@ public class WatchUpdaterService extends WearableListenerService implements private String generateCOBString() { String cobStringResult = "--"; - AutosensData autosensData = IobCobCalculatorPlugin.getLastAutosensData("WatcherUpdaterService"); - if (autosensData != null) { - cobStringResult = (int) autosensData.cob + "g"; + CobInfo cobInfo = IobCobCalculatorPlugin.getPlugin().getCobInfo(false, "WatcherUpdaterService"); + if (cobInfo.displayCob != null) { + cobStringResult = DecimalFormatter.to0Decimal(cobInfo.displayCob); + if (cobInfo.futureCarbs > 0) { + cobStringResult += "(" + DecimalFormatter.to0Decimal(cobInfo.futureCarbs) + ")"; + } + cobStringResult += "g"; } return cobStringResult; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/XDripStatusline/StatuslinePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/XDripStatusline/StatuslinePlugin.java index 5fb902c2eb..5ce277e399 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/XDripStatusline/StatuslinePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/XDripStatusline/StatuslinePlugin.java @@ -9,7 +9,6 @@ import android.support.annotation.NonNull; import com.squareup.otto.Subscribe; -import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.IobTotal; @@ -22,28 +21,19 @@ import info.nightscout.androidaps.events.EventRefreshOverview; import info.nightscout.androidaps.events.EventTempBasalChange; import info.nightscout.androidaps.events.EventTreatmentChange; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.TreatmentsInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Loop.LoopPlugin; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.utils.DecimalFormatter; /** * Created by adrian on 17/11/16. */ -public class StatuslinePlugin implements PluginBase { - - //broadcast related constants - private static final String EXTRA_STATUSLINE = "com.eveningoutpost.dexdrip.Extras.Statusline"; - private static final String ACTION_NEW_EXTERNAL_STATUSLINE = "com.eveningoutpost.dexdrip.ExternalStatusline"; - private static final String RECEIVER_PERMISSION = "com.eveningoutpost.dexdrip.permissions.RECEIVE_EXTERNAL_STATUSLINE"; - - - private boolean fragmentEnabled = false; - private boolean lastLoopStatus; - - private final Context ctx; - private SharedPreferences mPrefs; +public class StatuslinePlugin extends PluginBase { private static StatuslinePlugin statuslinePlugin; @@ -51,8 +41,19 @@ public class StatuslinePlugin implements PluginBase { return statuslinePlugin; } - public static StatuslinePlugin initPlugin(Context ctx) { + //broadcast related constants + private static final String EXTRA_STATUSLINE = "com.eveningoutpost.dexdrip.Extras.Statusline"; + private static final String ACTION_NEW_EXTERNAL_STATUSLINE = "com.eveningoutpost.dexdrip.ExternalStatusline"; + private static final String RECEIVER_PERMISSION = "com.eveningoutpost.dexdrip.permissions.RECEIVE_EXTERNAL_STATUSLINE"; + + private boolean lastLoopStatus; + + private final Context ctx; + private SharedPreferences mPrefs; + + + public static StatuslinePlugin initPlugin(Context ctx) { if (statuslinePlugin == null) { statuslinePlugin = new StatuslinePlugin(ctx); } @@ -60,103 +61,40 @@ public class StatuslinePlugin implements PluginBase { return statuslinePlugin; } - private StatuslinePlugin(Context ctx) { + public StatuslinePlugin(Context ctx) { + super(new PluginDescription() + .mainType(PluginType.GENERAL) + .pluginName(R.string.xdripstatus) + .shortName(R.string.xdripstatus_shortname) + .neverVisible(true) + .preferencesId(R.xml.pref_xdripstatus) + ); this.ctx = ctx; this.mPrefs = PreferenceManager.getDefaultSharedPreferences(ctx); } @Override - public int getType() { - return PluginBase.GENERAL; + protected void onStart() { + MainApp.bus().register(this); + sendStatus(); + super.onStart(); } @Override - public String getFragmentClass() { - return null; + protected void onStop() { + super.onStop(); + MainApp.bus().unregister(this); + sendStatus(); } - @Override - public String getName() { - return ctx.getString(R.string.xdripstatus); - } - - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.xdripstatus_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - return type == GENERAL && fragmentEnabled; - } - - @Override - public boolean isVisibleInTabs(int type) { - return false; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return false; - } - - @Override - public boolean showInList(int type) { - return !Config.NSCLIENT && !Config.G5UPLOADER; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == GENERAL) { - this.fragmentEnabled = fragmentEnabled; - - if (fragmentEnabled) { - try { - MainApp.bus().register(this); - } catch (Exception e) { - } - sendStatus(); - } else { - try { - MainApp.bus().unregister(this); - } catch (Exception e) { - } - sendStatus(); - } - } - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - // do nothing, no gui - } - - @Override - public int getPreferencesId() { - return R.xml.pref_xdripstatus; - } - - private void sendStatus() { - - String status = ""; // sent once on disable - if (fragmentEnabled) { - status = buildStatusString(); - } + Profile profile = MainApp.getConfigBuilder().getProfile(); + if (isEnabled(PluginType.GENERAL) && profile != null) { + status = buildStatusString(profile); + } //sendData final Bundle bundle = new Bundle(); @@ -168,23 +106,23 @@ public class StatuslinePlugin implements PluginBase { } @NonNull - private String buildStatusString() { + private String buildStatusString(Profile profile) { String status = ""; - LoopPlugin activeloop = ConfigBuilderPlugin.getActiveLoop(); + LoopPlugin loopPlugin = LoopPlugin.getPlugin(); - if (activeloop != null && !activeloop.isEnabled(PluginBase.LOOP)) { + if (!loopPlugin.isEnabled(PluginType.LOOP)) { status += ctx.getString(R.string.disabledloop) + "\n"; lastLoopStatus = false; - } else if (activeloop != null && activeloop.isEnabled(PluginBase.LOOP)) { + } else if (loopPlugin.isEnabled(PluginType.LOOP)) { lastLoopStatus = true; } //Temp basal - TreatmentsInterface treatmentsInterface = MainApp.getConfigBuilder(); + TreatmentsInterface treatmentsInterface = TreatmentsPlugin.getPlugin(); TemporaryBasal activeTemp = treatmentsInterface.getTempBasalFromHistory(System.currentTimeMillis()); if (activeTemp != null) { - status += activeTemp.toStringShort(); + status += activeTemp.toStringShort() + " "; } //IOB @@ -200,10 +138,6 @@ public class StatuslinePlugin implements PluginBase { + DecimalFormatter.to2Decimal(bolusIob.iob) + "|" + DecimalFormatter.to2Decimal(basalIob.basaliob) + ")"; } - Profile profile = MainApp.getConfigBuilder().getProfile(); - - if (profile == null) - return status; if (!mPrefs.getBoolean("xdripstatus_showbgi", false)) { return status; @@ -245,20 +179,10 @@ public class StatuslinePlugin implements PluginBase { @Subscribe public void onStatusEvent(final EventRefreshOverview ev) { - //Filter events where loop is (de)activated - - LoopPlugin activeloop = ConfigBuilderPlugin.getActiveLoop(); - if (activeloop == null) return; - - if ((lastLoopStatus != activeloop.isEnabled(PluginBase.LOOP))) { + if ((lastLoopStatus != LoopPlugin.getPlugin().isEnabled(PluginType.LOOP))) { sendStatus(); } } - - public boolean isEnabled() { - return fragmentEnabled; - } - } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java index 04da1cd9a9..93b6f99db0 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java @@ -2,6 +2,7 @@ package info.nightscout.androidaps.queue; import android.content.Context; import android.content.Intent; +import android.os.SystemClock; import android.support.v7.app.AppCompatActivity; import android.text.Html; import android.text.Spanned; @@ -17,13 +18,14 @@ import info.nightscout.androidaps.data.DetailedBolusInfo; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.events.EventBolusRequested; +import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Overview.Dialogs.BolusProgressDialog; import info.nightscout.androidaps.plugins.Overview.Dialogs.BolusProgressHelperActivity; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.queue.commands.Command; import info.nightscout.androidaps.queue.commands.CommandBolus; import info.nightscout.androidaps.queue.commands.CommandCancelExtendedBolus; @@ -31,7 +33,9 @@ import info.nightscout.androidaps.queue.commands.CommandCancelTempBasal; import info.nightscout.androidaps.queue.commands.CommandExtendedBolus; import info.nightscout.androidaps.queue.commands.CommandLoadEvents; import info.nightscout.androidaps.queue.commands.CommandLoadHistory; +import info.nightscout.androidaps.queue.commands.CommandLoadTDDs; import info.nightscout.androidaps.queue.commands.CommandReadStatus; +import info.nightscout.androidaps.queue.commands.CommandSMBBolus; import info.nightscout.androidaps.queue.commands.CommandSetProfile; import info.nightscout.androidaps.queue.commands.CommandTempBasalAbsolute; import info.nightscout.androidaps.queue.commands.CommandTempBasalPercent; @@ -103,10 +107,12 @@ public class CommandQueue { private synchronized void inject(Command command) { // inject as a first command + log.debug("QUEUE: Adding as first: " + command.getClass().getSimpleName() + " - " + command.status()); queue.addFirst(command); } private synchronized void add(Command command) { + log.debug("QUEUE: Adding: " + command.getClass().getSimpleName() + " - " + command.status()); queue.add(command); } @@ -138,9 +144,16 @@ public class CommandQueue { // After new command added to the queue // start thread again if not already running protected synchronized void notifyAboutNewCommand() { + while (thread != null && thread.getState() != Thread.State.TERMINATED && thread.waitingForDisconnect) { + log.debug("QUEUE: Waiting for previous thread finish"); + SystemClock.sleep(500); + } if (thread == null || thread.getState() == Thread.State.TERMINATED) { thread = new QueueThread(this); thread.start(); + log.debug("QUEUE: Starting new thread"); + } else { + log.debug("QUEUE: Thread is already running"); } } @@ -151,36 +164,48 @@ public class CommandQueue { // returns true if command is queued public boolean bolus(DetailedBolusInfo detailedBolusInfo, Callback callback) { - if (isRunning(Command.CommandType.BOLUS)) { - if (callback != null) - callback.result(executingNowError()).run(); - return false; - } + Command.CommandType type = detailedBolusInfo.isSMB ? Command.CommandType.SMB_BOLUS : Command.CommandType.BOLUS; - // remove all unfinished boluses - removeAll(Command.CommandType.BOLUS); + if(type.equals(Command.CommandType.BOLUS) && detailedBolusInfo.carbs > 0 && detailedBolusInfo.insulin == 0){ + type = Command.CommandType.CARBS_ONLY_TREATMENT; + //Carbs only can be added in parallel as they can be "in the future". + } else { + if (isRunning(type)) { + if (callback != null) + callback.result(executingNowError()).run(); + return false; + } + + // remove all unfinished boluses + removeAll(type); + } // apply constraints - detailedBolusInfo.insulin = MainApp.getConfigBuilder().applyBolusConstraints(detailedBolusInfo.insulin); - detailedBolusInfo.carbs = MainApp.getConfigBuilder().applyCarbsConstraints((int) detailedBolusInfo.carbs); + detailedBolusInfo.insulin = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(detailedBolusInfo.insulin)).value(); + detailedBolusInfo.carbs = MainApp.getConstraintChecker().applyCarbsConstraints(new Constraint<>((int) detailedBolusInfo.carbs)).value(); // add new command to queue - add(new CommandBolus(detailedBolusInfo, callback)); + if (detailedBolusInfo.isSMB) { + add(new CommandSMBBolus(detailedBolusInfo, callback)); + } else { + add(new CommandBolus(detailedBolusInfo, callback, type)); + if(type.equals(Command.CommandType.BOLUS)) { + // Bring up bolus progress dialog (start here, so the dialog is shown when the bolus is requested, + // not when the Bolus command is starting. The command closes the dialog upon completion). + showBolusProgressDialog(detailedBolusInfo.insulin, detailedBolusInfo.context); + // Notify Wear about upcoming bolus + MainApp.bus().post(new EventBolusRequested(detailedBolusInfo.insulin)); + } + } notifyAboutNewCommand(); - // Notify Wear about upcoming bolus - MainApp.bus().post(new EventBolusRequested(detailedBolusInfo.insulin)); - - // Bring up bolus progress dialog - showBolusProgressDialog(detailedBolusInfo.insulin, detailedBolusInfo.context); - return true; } // returns true if command is queued - public boolean tempBasalAbsolute(double absoluteRate, int durationInMinutes, boolean enforceNew, Callback callback) { - if (isRunning(Command.CommandType.TEMPBASAL)) { + public boolean tempBasalAbsolute(double absoluteRate, int durationInMinutes, boolean enforceNew, Profile profile, Callback callback) { + if (!enforceNew && isRunning(Command.CommandType.TEMPBASAL)) { if (callback != null) callback.result(executingNowError()).run(); return false; @@ -189,10 +214,10 @@ public class CommandQueue { // remove all unfinished removeAll(Command.CommandType.TEMPBASAL); - Double rateAfterConstraints = MainApp.getConfigBuilder().applyBasalConstraints(absoluteRate); + Double rateAfterConstraints = MainApp.getConstraintChecker().applyBasalConstraints(new Constraint<>(absoluteRate), profile).value(); // add new command to queue - add(new CommandTempBasalAbsolute(rateAfterConstraints, durationInMinutes, enforceNew, callback)); + add(new CommandTempBasalAbsolute(rateAfterConstraints, durationInMinutes, enforceNew, profile, callback)); notifyAboutNewCommand(); @@ -200,8 +225,8 @@ public class CommandQueue { } // returns true if command is queued - public boolean tempBasalPercent(int percent, int durationInMinutes, boolean enforceNew, Callback callback) { - if (isRunning(Command.CommandType.TEMPBASAL)) { + public boolean tempBasalPercent(Integer percent, int durationInMinutes, boolean enforceNew, Profile profile, Callback callback) { + if (!enforceNew && isRunning(Command.CommandType.TEMPBASAL)) { if (callback != null) callback.result(executingNowError()).run(); return false; @@ -210,10 +235,10 @@ public class CommandQueue { // remove all unfinished removeAll(Command.CommandType.TEMPBASAL); - Integer percentAfterConstraints = MainApp.getConfigBuilder().applyBasalConstraints(percent); + Integer percentAfterConstraints = MainApp.getConstraintChecker().applyBasalPercentConstraints(new Constraint<>(percent), profile).value(); // add new command to queue - add(new CommandTempBasalPercent(percentAfterConstraints, durationInMinutes, enforceNew, callback)); + add(new CommandTempBasalPercent(percentAfterConstraints, durationInMinutes, enforceNew, profile, callback)); notifyAboutNewCommand(); @@ -228,7 +253,7 @@ public class CommandQueue { return false; } - Double rateAfterConstraints = MainApp.getConfigBuilder().applyBolusConstraints(insulin); + Double rateAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(new Constraint<>(insulin)).value(); // remove all unfinished removeAll(Command.CommandType.EXTENDEDBOLUS); @@ -243,7 +268,7 @@ public class CommandQueue { // returns true if command is queued public boolean cancelTempBasal(boolean enforceNew, Callback callback) { - if (isRunning(Command.CommandType.TEMPBASAL)) { + if (!enforceNew && isRunning(Command.CommandType.TEMPBASAL)) { if (callback != null) callback.result(executingNowError()).run(); return false; @@ -281,9 +306,18 @@ public class CommandQueue { // returns true if command is queued public boolean setProfile(Profile profile, Callback callback) { - if (isRunning(Command.CommandType.BASALPROFILE)) { + if (isThisProfileSet(profile)) { + log.debug("QUEUE: Correct profile already set"); if (callback != null) - callback.result(executingNowError()).run(); + callback.result(new PumpEnactResult().success(true).enacted(false)).run(); + return false; + } + + if (!MainApp.isEngineeringModeOrRelease()) { + Notification notification = new Notification(Notification.NOT_ENG_MODE_OR_RELEASE, MainApp.sResources.getString(R.string.not_eng_mode_or_release), Notification.URGENT); + MainApp.bus().post(new EventNewNotification(notification)); + if (callback != null) + callback.result(new PumpEnactResult().success(false).comment(MainApp.sResources.getString(R.string.not_eng_mode_or_release))).run(); return false; } @@ -291,8 +325,8 @@ public class CommandQueue { Profile.BasalValue[] basalValues = profile.getBasalValues(); PumpInterface pump = ConfigBuilderPlugin.getActivePump(); - for (int index = 0; index < basalValues.length; index++) { - if (basalValues[index].value < pump.getPumpDescription().basalMinimumRate) { + for (Profile.BasalValue basalValue : basalValues) { + if (basalValue.value < pump.getPumpDescription().basalMinimumRate) { Notification notification = new Notification(Notification.BASAL_VALUE_BELOW_MINIMUM, MainApp.sResources.getString(R.string.basalvaluebelowminimum), Notification.URGENT); MainApp.bus().post(new EventNewNotification(notification)); if (callback != null) @@ -303,13 +337,6 @@ public class CommandQueue { MainApp.bus().post(new EventDismissNotification(Notification.BASAL_VALUE_BELOW_MINIMUM)); - if (isThisProfileSet(profile)) { - log.debug("Correct profile already set"); - if (callback != null) - callback.result(new PumpEnactResult().success(true).enacted(false)).run(); - return false; - } - // remove all unfinished removeAll(Command.CommandType.BASALPROFILE); @@ -360,6 +387,25 @@ public class CommandQueue { return true; } + // returns true if command is queued + public boolean loadTDDs(Callback callback) { + if (isRunning(Command.CommandType.LOADHISTORY)) { + if (callback != null) + callback.result(executingNowError()).run(); + return false; + } + + // remove all unfinished + removeAll(Command.CommandType.LOADHISTORY); + + // add new command to queue + add(new CommandLoadTDDs(callback)); + + notifyAboutNewCommand(); + + return true; + } + // returns true if command is queued public boolean loadEvents(Callback callback) { if (isRunning(Command.CommandType.LOADEVENTS)) { @@ -397,10 +443,11 @@ public class CommandQueue { public boolean isThisProfileSet(Profile profile) { PumpInterface activePump = ConfigBuilderPlugin.getActivePump(); - if (activePump != null) { + Profile current = MainApp.getConfigBuilder().getProfile(); + if (activePump != null && current != null) { boolean result = activePump.isThisProfileSet(profile); if (!result) { - log.debug("Current profile: " + MainApp.getConfigBuilder().getProfile().getData().toString()); + log.debug("Current profile: " + current.getData().toString()); log.debug("New profile: " + profile.getData().toString()); } return result; diff --git a/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java b/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java index c5fb9821bc..8ec61932c8 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java @@ -28,11 +28,11 @@ import info.nightscout.utils.SP; public class QueueThread extends Thread { private static Logger log = LoggerFactory.getLogger(QueueThread.class); - CommandQueue queue; + private CommandQueue queue; - private long connectionStartTime = 0; private long lastCommandTime = 0; private boolean connectLogged = false; + public boolean waitingForDisconnect = false; private PowerManager.WakeLock mWakeLock; @@ -41,18 +41,24 @@ public class QueueThread extends Thread { this.queue = queue; PowerManager powerManager = (PowerManager) MainApp.instance().getApplicationContext().getSystemService(Context.POWER_SERVICE); - mWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "QueueThread"); + mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "QueueThread"); } @Override public final void run() { mWakeLock.acquire(); MainApp.bus().post(new EventQueueChanged()); - connectionStartTime = lastCommandTime = System.currentTimeMillis(); + long connectionStartTime = lastCommandTime = System.currentTimeMillis(); try { while (true) { PumpInterface pump = ConfigBuilderPlugin.getActivePump(); + if (pump == null) { + log.debug("QUEUE: pump == null"); + MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.pumpNotInitialized))); + SystemClock.sleep(1000); + continue; + } long secondsElapsed = (System.currentTimeMillis() - connectionStartTime) / 1000; if (!pump.isConnected() && secondsElapsed > Constants.PUMP_MAX_CONNECTION_TIME_IN_SECONDS) { @@ -131,10 +137,12 @@ public class QueueThread extends Thread { if (queue.size() == 0 && queue.performing() == null) { long secondsFromLastCommand = (System.currentTimeMillis() - lastCommandTime) / 1000; if (secondsFromLastCommand >= 5) { + waitingForDisconnect = true; log.debug("QUEUE: queue empty. disconnect"); MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); pump.disconnect("Queue empty"); MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED)); + log.debug("QUEUE: disconnected"); return; } else { log.debug("QUEUE: waiting for disconnect"); diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/Command.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/Command.java index 5129c7983f..b865fac86e 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/commands/Command.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/Command.java @@ -11,11 +11,13 @@ import info.nightscout.androidaps.queue.Callback; public abstract class Command { public enum CommandType { BOLUS, + SMB_BOLUS, + CARBS_ONLY_TREATMENT, TEMPBASAL, EXTENDEDBOLUS, BASALPROFILE, READSTATUS, - LOADHISTORY, // so far only Dana specific + LOADHISTORY, // TDDs and so far only Dana specific LOADEVENTS // so far only Dana specific } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandBolus.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandBolus.java index 278fd7681b..2be3831ace 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandBolus.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandBolus.java @@ -16,8 +16,8 @@ import info.nightscout.utils.DecimalFormatter; public class CommandBolus extends Command { DetailedBolusInfo detailedBolusInfo; - public CommandBolus(DetailedBolusInfo detailedBolusInfo, Callback callback) { - commandType = CommandType.BOLUS; + public CommandBolus(DetailedBolusInfo detailedBolusInfo, Callback callback, CommandType type) { + commandType = type; this.detailedBolusInfo = detailedBolusInfo; this.callback = callback; } @@ -34,6 +34,7 @@ public class CommandBolus extends Command { } public String status() { - return "BOLUS " + DecimalFormatter.to1Decimal(detailedBolusInfo.insulin) + "U"; + return (detailedBolusInfo.insulin > 0 ? "BOLUS " + DecimalFormatter.to1Decimal(detailedBolusInfo.insulin) + "U " : "") + + (detailedBolusInfo.carbs > 0 ? "CARBS " + DecimalFormatter.to0Decimal(detailedBolusInfo.carbs) + "g" : "" ); } } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandLoadTDDs.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandLoadTDDs.java new file mode 100644 index 0000000000..363781a379 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandLoadTDDs.java @@ -0,0 +1,31 @@ +package info.nightscout.androidaps.queue.commands; + +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.queue.Callback; + +/** + * Created by mike on 10.11.2017. + */ + +public class CommandLoadTDDs extends Command { + + public CommandLoadTDDs(Callback callback) { + commandType = CommandType.LOADHISTORY; //belongs to the history group of commands + this.callback = callback; + } + + @Override + public void execute() { + PumpInterface pump = ConfigBuilderPlugin.getActivePump(); + PumpEnactResult r = pump.loadTDDs(); + if (callback != null) + callback.result(r).run(); + } + + @Override + public String status() { + return "LOADTDDS"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSMBBolus.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSMBBolus.java new file mode 100644 index 0000000000..46336f7d4b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSMBBolus.java @@ -0,0 +1,53 @@ +package info.nightscout.androidaps.queue.commands; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.Overview.Dialogs.BolusProgressDialog; +import info.nightscout.androidaps.plugins.Overview.events.EventDismissBolusprogressIfRunning; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; +import info.nightscout.androidaps.queue.Callback; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.T; + +/** + * Created by mike on 09.11.2017. + */ + +public class CommandSMBBolus extends Command { + private static Logger log = LoggerFactory.getLogger(CommandSMBBolus.class); + DetailedBolusInfo detailedBolusInfo; + + public CommandSMBBolus(DetailedBolusInfo detailedBolusInfo, Callback callback) { + commandType = CommandType.SMB_BOLUS; + this.detailedBolusInfo = detailedBolusInfo; + this.callback = callback; + } + + @Override + public void execute() { + PumpEnactResult r; + long lastBolusTime = TreatmentsPlugin.getPlugin().getLastBolusTime(); + if (lastBolusTime != 0 && lastBolusTime + T.mins(3).msecs() > DateUtil.now()) { + log.debug("SMB requsted but still in 3 min interval"); + r = new PumpEnactResult().enacted(false).success(false).comment("SMB requsted but still in 3 min interval"); + } else if (detailedBolusInfo.deliverAt != 0 && detailedBolusInfo.deliverAt + T.mins(1).msecs() > System.currentTimeMillis()) + r = ConfigBuilderPlugin.getActivePump().deliverTreatment(detailedBolusInfo); + else { + r = new PumpEnactResult().enacted(false).success(false).comment("SMB request too old"); + log.debug("SMB bolus canceled. delivetAt=" + detailedBolusInfo.deliverAt + " now=" + System.currentTimeMillis()); + } + + if (callback != null) + callback.result(r).run(); + } + + public String status() { + return "SMBBOLUS " + DecimalFormatter.to1Decimal(detailedBolusInfo.insulin) + "U"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSetProfile.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSetProfile.java index fe236fa55f..8a8bc41dfa 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSetProfile.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSetProfile.java @@ -1,9 +1,18 @@ package info.nightscout.androidaps.queue.commands; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.db.ProfileSwitch; +import info.nightscout.androidaps.db.Source; +import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.SmsCommunicator.SmsCommunicatorPlugin; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.androidaps.queue.Callback; /** @@ -11,7 +20,8 @@ import info.nightscout.androidaps.queue.Callback; */ public class CommandSetProfile extends Command { - Profile profile; + private static Logger log = LoggerFactory.getLogger(CommandSetProfile.class); + private Profile profile; public CommandSetProfile(Profile profile, Callback callback) { commandType = CommandType.BASALPROFILE; @@ -21,9 +31,25 @@ public class CommandSetProfile extends Command { @Override public void execute() { + if (ConfigBuilderPlugin.getCommandQueue().isThisProfileSet(profile)) { + log.debug("QUEUE: Correct profile already set"); + if (callback != null) + callback.result(new PumpEnactResult().success(true).enacted(false)).run(); + return; + } + PumpEnactResult r = ConfigBuilderPlugin.getActivePump().setNewBasalProfile(profile); if (callback != null) callback.result(r).run(); + + // Send SMS notification if ProfileSwitch is comming from NS + ProfileSwitch profileSwitch = TreatmentsPlugin.getPlugin().getProfileSwitchFromHistory(System.currentTimeMillis()); + if (r.enacted && profileSwitch.source == Source.NIGHTSCOUT) { + SmsCommunicatorPlugin smsCommunicatorPlugin = MainApp.getSpecificPlugin(SmsCommunicatorPlugin.class); + if (smsCommunicatorPlugin != null && smsCommunicatorPlugin.isEnabled(PluginType.GENERAL)) { + smsCommunicatorPlugin.sendNotificationToAllNumbers(MainApp.sResources.getString(R.string.profile_set_ok)); + } + } } @Override diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalAbsolute.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalAbsolute.java index 28dc728174..35c2435558 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalAbsolute.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalAbsolute.java @@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.queue.Callback; @@ -19,18 +20,20 @@ public class CommandTempBasalAbsolute extends Command { int durationInMinutes; double absoluteRate; boolean enforceNew; + Profile profile; - public CommandTempBasalAbsolute(double absoluteRate, int durationInMinutes, boolean enforceNew, Callback callback) { + public CommandTempBasalAbsolute(double absoluteRate, int durationInMinutes, boolean enforceNew, Profile profile, Callback callback) { commandType = CommandType.TEMPBASAL; this.absoluteRate = absoluteRate; this.durationInMinutes = durationInMinutes; this.enforceNew = enforceNew; + this.profile = profile; this.callback = callback; } @Override public void execute() { - PumpEnactResult r = ConfigBuilderPlugin.getActivePump().setTempBasalAbsolute(absoluteRate, durationInMinutes, enforceNew); + PumpEnactResult r = ConfigBuilderPlugin.getActivePump().setTempBasalAbsolute(absoluteRate, durationInMinutes, profile, enforceNew); if (Config.logCongigBuilderActions) log.debug("setTempBasalAbsolute rate: " + absoluteRate + " durationInMinutes: " + durationInMinutes + " success: " + r.success + " enacted: " + r.enacted); if (callback != null) diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalPercent.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalPercent.java index e037f0b869..bbb421e128 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalPercent.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalPercent.java @@ -4,7 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import info.nightscout.androidaps.Config; -import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.queue.Callback; @@ -19,18 +19,20 @@ public class CommandTempBasalPercent extends Command { int durationInMinutes; int percent; boolean enforceNew; + Profile profile; - public CommandTempBasalPercent(int percent, int durationInMinutes, boolean enforceNew, Callback callback) { + public CommandTempBasalPercent(int percent, int durationInMinutes, boolean enforceNew, Profile profile, Callback callback) { commandType = CommandType.TEMPBASAL; this.percent = percent; this.durationInMinutes = durationInMinutes; this.enforceNew = enforceNew; + this.profile = profile; this.callback = callback; } @Override public void execute() { - PumpEnactResult r = ConfigBuilderPlugin.getActivePump().setTempBasalPercent(percent, durationInMinutes, enforceNew); + PumpEnactResult r = ConfigBuilderPlugin.getActivePump().setTempBasalPercent(percent, durationInMinutes, profile, enforceNew); if (Config.logCongigBuilderActions) log.debug("setTempBasalPercent percent: " + percent + " durationInMinutes: " + durationInMinutes + " success: " + r.success + " enacted: " + r.enacted); if (callback != null) diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.java index 3d3258c614..af46321901 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.java +++ b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.java @@ -18,11 +18,12 @@ import java.util.Date; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.events.EventProfileSwitchChange; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.queue.commands.Command; import info.nightscout.utils.LocalAlertUtils; -import info.nightscout.utils.SP; public class KeepAliveReceiver extends BroadcastReceiver { private static Logger log = LoggerFactory.getLogger(KeepAliveReceiver.class); @@ -52,19 +53,19 @@ public class KeepAliveReceiver extends BroadcastReceiver { private void checkPump() { final PumpInterface pump = ConfigBuilderPlugin.getActivePump(); final Profile profile = MainApp.getConfigBuilder().getProfile(); - if (pump != null && profile != null && profile.getBasal() != null) { + if (pump != null && profile != null) { Date lastConnection = pump.lastDataTime(); boolean isStatusOutdated = lastConnection.getTime() + STATUS_UPDATE_FREQUENCY < System.currentTimeMillis(); boolean isBasalOutdated = Math.abs(profile.getBasal() - pump.getBaseBasalRate()) > pump.getPumpDescription().basalStep; LocalAlertUtils.checkPumpUnreachableAlarm(lastConnection, isStatusOutdated); - if (!pump.isThisProfileSet(profile)) { - MainApp.getConfigBuilder().getCommandQueue().setProfile(profile, null); + if (!pump.isThisProfileSet(profile) && !ConfigBuilderPlugin.getCommandQueue().isRunning(Command.CommandType.BASALPROFILE)) { + MainApp.bus().post(new EventProfileSwitchChange()); } else if (isStatusOutdated && !pump.isBusy()) { - MainApp.getConfigBuilder().getCommandQueue().readStatus("KeepAlive. Status outdated.", null); + ConfigBuilderPlugin.getCommandQueue().readStatus("KeepAlive. Status outdated.", null); } else if (isBasalOutdated && !pump.isBusy()) { - MainApp.getConfigBuilder().getCommandQueue().readStatus("KeepAlive. Basal outdated.", null); + ConfigBuilderPlugin.getCommandQueue().readStatus("KeepAlive. Basal outdated.", null); } } } diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java new file mode 100644 index 0000000000..b3a99b66ff --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java @@ -0,0 +1,67 @@ +package info.nightscout.androidaps.receivers; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.wifi.SupplicantState; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.PowerManager; +import android.support.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.events.EventNetworkChange; + +public class NetworkChangeReceiver extends BroadcastReceiver { + private static Logger log = LoggerFactory.getLogger(NetworkChangeReceiver.class); + + @Override + public void onReceive(final Context context, final Intent intent) { + PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + if (pm == null) return; + PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "NetworkChangeReceiver"); + wl.acquire(10000); + + EventNetworkChange event = grabNetworkStatus(); + if (event != null) + MainApp.bus().post(event); + + wl.release(); + } + + @Nullable + public static EventNetworkChange grabNetworkStatus() { + EventNetworkChange event = new EventNetworkChange(); + + ConnectivityManager cm = (ConnectivityManager) MainApp.instance().getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); + if (cm == null) return null; + NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + + if (activeNetwork != null) { + if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) { + event.wifiConnected = true; + WifiManager wifiManager = (WifiManager) MainApp.instance().getApplicationContext().getSystemService(Context.WIFI_SERVICE); + if (wifiManager != null) { + WifiInfo wifiInfo = wifiManager.getConnectionInfo(); + if (wifiInfo.getSupplicantState() == SupplicantState.COMPLETED) { + event.ssid = wifiInfo.getSSID(); + } + log.debug("NETCHANGE: Wifi connected. SSID: " + event.ssid); + } + } + if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) { + event.mobileConnected = true; + event.roaming = activeNetwork.isRoaming(); + log.debug("NETCHANGE: Mobile connected. Roaming: " + event.roaming); + } + } else { + log.debug("NETCHANGE: Disconnected."); + } + return event; + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/tabs/TabPageAdapter.java b/app/src/main/java/info/nightscout/androidaps/tabs/TabPageAdapter.java index b5ed76af02..8397da97aa 100644 --- a/app/src/main/java/info/nightscout/androidaps/tabs/TabPageAdapter.java +++ b/app/src/main/java/info/nightscout/androidaps/tabs/TabPageAdapter.java @@ -14,7 +14,6 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; -import info.nightscout.androidaps.MainActivity; import info.nightscout.androidaps.interfaces.PluginBase; /** @@ -37,7 +36,7 @@ public class TabPageAdapter extends FragmentStatePagerAdapter { @Nullable public Fragment getItem(int position) { //Fragment fragment = (Fragment) visibleFragmentList.get(position); - return Fragment.instantiate(context, visibleFragmentList.get(position).getFragmentClass()); + return Fragment.instantiate(context, visibleFragmentList.get(position).pluginDescription.getFragmentClass()); } @Override @@ -67,7 +66,7 @@ public class TabPageAdapter extends FragmentStatePagerAdapter { } public void registerNewFragment(PluginBase plugin) { - if (plugin.hasFragment() && plugin.isVisibleInTabs(plugin.getType())) { + if (plugin.hasFragment() && plugin.isFragmentVisible()) { visibleFragmentList.add(plugin); notifyDataSetChanged(); } diff --git a/app/src/main/java/info/nightscout/utils/BolusWizard.java b/app/src/main/java/info/nightscout/utils/BolusWizard.java index ac710dc51f..3563a8a55d 100644 --- a/app/src/main/java/info/nightscout/utils/BolusWizard.java +++ b/app/src/main/java/info/nightscout/utils/BolusWizard.java @@ -1,12 +1,12 @@ package info.nightscout.utils; -import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.data.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.TempTarget; import info.nightscout.androidaps.interfaces.TreatmentsInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; /** * Created by mike on 11.10.2016. @@ -93,7 +93,7 @@ public class BolusWizard { // Insulin from IOB // IOB calculation - TreatmentsInterface treatments = MainApp.getConfigBuilder(); + TreatmentsInterface treatments = TreatmentsPlugin.getPlugin(); treatments.updateTotalIOBTreatments(); IobTotal bolusIob = treatments.getLastCalculationTreatments().round(); treatments.updateTotalIOBTempBasals(); diff --git a/app/src/main/java/info/nightscout/utils/DateUtil.java b/app/src/main/java/info/nightscout/utils/DateUtil.java index 86965621d2..441449e8ee 100644 --- a/app/src/main/java/info/nightscout/utils/DateUtil.java +++ b/app/src/main/java/info/nightscout/utils/DateUtil.java @@ -4,6 +4,7 @@ import android.support.v4.util.LongSparseArray; import android.text.format.DateUtils; import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.ISODateTimeFormat; @@ -73,6 +74,7 @@ public class DateUtil { public static Date toDate(Integer seconds) { Calendar calendar = new GregorianCalendar(); + calendar.set(Calendar.MONTH, 0); // Set january to be sure we miss DST changing calendar.set(Calendar.HOUR_OF_DAY, seconds / 60 / 60); calendar.set(Calendar.MINUTE, (seconds / 60) % 60); calendar.set(Calendar.SECOND, 0); @@ -107,24 +109,35 @@ public class DateUtil { } public static String timeString(Date date) { - return DateUtils.formatDateTime(MainApp.instance(), date.getTime(), DateUtils.FORMAT_SHOW_TIME); + //return DateUtils.formatDateTime(MainApp.instance(), date.getTime(), DateUtils.FORMAT_SHOW_TIME); + return new DateTime(date).toString(DateTimeFormat.shortTime()); } public static String timeString(long mills) { - return DateUtils.formatDateTime(MainApp.instance(), mills, DateUtils.FORMAT_SHOW_TIME); + //return DateUtils.formatDateTime(MainApp.instance(), mills, DateUtils.FORMAT_SHOW_TIME); + return new DateTime(mills).toString(DateTimeFormat.shortTime()); } public static String dateAndTimeString(Date date) { return dateString(date) + " " + timeString(date); } + public static String dateAndTimeRangeString(long start, long end) { + return dateAndTimeString(start) + " - " + timeString(end); + } + public static String dateAndTimeString(long mills) { return dateString(mills) + " " + timeString(mills); } public static String minAgo(long time) { - int mins = (int) ((System.currentTimeMillis() - time) / 1000 / 60); - return String.format(MainApp.sResources.getString(R.string.minago), mins); + int mins = (int) ((now() - time) / 1000 / 60); + return MainApp.gs(R.string.minago, mins); + } + + public static String hourAgo(long time) { + double hours = (now() - time) / 1000d / 60 / 60; + return MainApp.gs(R.string.hoursago, hours); } private static LongSparseArray timeStrings = new LongSparseArray<>(); @@ -133,7 +146,7 @@ public class DateUtil { String cached = timeStrings.get(seconds); if (cached != null) return cached; - String t = DateUtils.formatDateTime(MainApp.instance(), toDate(seconds).getTime(), DateUtils.FORMAT_SHOW_TIME); + String t = timeString(toDate(seconds)); timeStrings.put(seconds, t); return t; } @@ -154,5 +167,12 @@ public class DateUtil { return timeFrameString(timestamp - System.currentTimeMillis()); } + public static long now() { + return System.currentTimeMillis(); + } + + public static long roundDateToSec(long date) { + return date - date % 1000; + } } diff --git a/app/src/main/java/info/nightscout/utils/DecimalFormatter.java b/app/src/main/java/info/nightscout/utils/DecimalFormatter.java index 1a70b43c30..0f7164b690 100644 --- a/app/src/main/java/info/nightscout/utils/DecimalFormatter.java +++ b/app/src/main/java/info/nightscout/utils/DecimalFormatter.java @@ -2,6 +2,8 @@ package info.nightscout.utils; import java.text.DecimalFormat; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; + /** * Created by mike on 11.07.2016. */ @@ -15,15 +17,43 @@ public class DecimalFormatter { return format0dec.format(value); } + public static String to0Decimal(double value, String unit) { + return format0dec.format(value) + unit; + } + public static String to1Decimal(double value) { return format1dec.format(value); } + public static String to1Decimal(double value, String unit) { + return format1dec.format(value) + unit; + } + public static String to2Decimal(double value) { return format2dec.format(value); } + public static String to2Decimal(double value, String unit) { + return format2dec.format(value) + unit; + } + public static String to3Decimal(double value) { return format3dec.format(value); } + + public static String to3Decimal(double value, String unit) { + return format3dec.format(value) + unit; + } + + public static String toPumpSupportedBolus(double value) { + return ConfigBuilderPlugin.getActivePump().getPumpDescription().bolusStep <= 0.05 + ? to2Decimal(value) + : to1Decimal(value); + } + + public static DecimalFormat pumpSupportedBolusFormat() { + return ConfigBuilderPlugin.getActivePump().getPumpDescription().bolusStep <= 0.05 + ? new DecimalFormat("0.00") + : new DecimalFormat("0.0"); + } } diff --git a/app/src/main/java/info/nightscout/utils/DefaultValueHelper.java b/app/src/main/java/info/nightscout/utils/DefaultValueHelper.java new file mode 100644 index 0000000000..ba1aba90b8 --- /dev/null +++ b/app/src/main/java/info/nightscout/utils/DefaultValueHelper.java @@ -0,0 +1,90 @@ +package info.nightscout.utils; + +import info.nightscout.androidaps.Constants; +import info.nightscout.androidaps.R; + +public class DefaultValueHelper { + + /** + * returns the corresponding EatingSoon TempTarget based on the given units (MMOL / MGDL) + * + * @param units + * @return + */ + public double getDefaultEatingSoonTT(String units) { + return Constants.MMOL.equals(units) ? Constants.defaultEatingSoonTTmmol + : Constants.defaultEatingSoonTTmgdl; + } + + /** + * returns the corresponding Activity TempTarget based on the given units (MMOL / MGDL) + * + * @param units + * @return + */ + public double getDefaultActivityTT(String units) { + return Constants.MMOL.equals(units) ? Constants.defaultActivityTTmmol + : Constants.defaultActivityTTmgdl; + } + + /** + * returns the corresponding Hypo TempTarget based on the given units (MMOL / MGDL) + * + * @param units + * @return + */ + public double getDefaultHypoTT(String units) { + return Constants.MMOL.equals(units) ? Constants.defaultHypoTTmmol + : Constants.defaultHypoTTmgdl; + } + + /** + * returns the configured EatingSoon TempTarget, if this is set to 0, the Default-Value is returned. + * + * @param units + * @return + */ + public double determineEatingSoonTT(String units) { + double value = SP.getDouble(R.string.key_eatingsoon_target, this.getDefaultEatingSoonTT(units)); + return value > 0 ? value : this.getDefaultEatingSoonTT(units); + } + + public int determineEatingSoonTTDuration() { + int value = SP.getInt(R.string.key_eatingsoon_duration, Constants.defaultEatingSoonTTDuration); + return value > 0 ? value : Constants.defaultEatingSoonTTDuration; + } + + + /** + * returns the configured Activity TempTarget, if this is set to 0, the Default-Value is returned. + * + * @param units + * @return + */ + public double determineActivityTT(String units) { + double value = SP.getDouble(R.string.key_activity_target, this.getDefaultActivityTT(units)); + return value > 0 ? value : this.getDefaultActivityTT(units); + } + + public int determineActivityTTDuration() { + int value = SP.getInt(R.string.key_activity_duration, Constants.defaultActivityTTDuration); + return value > 0 ? value : Constants.defaultActivityTTDuration; + } + + /** + * returns the configured Hypo TempTarget, if this is set to 0, the Default-Value is returned. + * + * @param units + * @return + */ + public double determineHypoTT(String units) { + double value = SP.getDouble(R.string.key_hypo_target, this.getDefaultHypoTT(units)); + return value > 0 ? value : this.getDefaultHypoTT(units); + } + + public int determineHypoTTDuration() { + int value = SP.getInt(R.string.key_hypo_duration, Constants.defaultHypoTTDuration); + return value > 0 ? value : Constants.defaultHypoTTDuration; + } + +} diff --git a/app/src/main/java/info/nightscout/utils/FabricPrivacy.java b/app/src/main/java/info/nightscout/utils/FabricPrivacy.java new file mode 100644 index 0000000000..bd117ed5c3 --- /dev/null +++ b/app/src/main/java/info/nightscout/utils/FabricPrivacy.java @@ -0,0 +1,83 @@ +package info.nightscout.utils; + +import com.crashlytics.android.Crashlytics; +import com.crashlytics.android.answers.Answers; +import com.crashlytics.android.answers.CustomEvent; + +/** + * Created by jamorham on 21/02/2018. + * + * Some users do not wish to be tracked, Fabric Answers and Crashlytics do not provide an easy way + * to disable them and make calls from a potentially invalid singleton reference. This wrapper + * emulates the methods but ignores the request if the instance is null or invalid. + * + */ + +public class FabricPrivacy { + + private static final String TAG = "FabricPrivacy"; + private static volatile FabricPrivacy instance; + + + public static FabricPrivacy getInstance() { + if (instance == null) { + initSelf(); + } + return instance; + } + + private static synchronized void initSelf() { + if (instance == null) { + instance = new FabricPrivacy(); + } + } + + // Crashlytics logException + public static void logException(Throwable throwable) { + try { + final Crashlytics crashlytics = Crashlytics.getInstance(); + crashlytics.core.logException(throwable); + } catch (NullPointerException | IllegalStateException e) { + android.util.Log.d(TAG, "Ignoring opted out non-initialized log: " + throwable); + } + } + + // Crashlytics log + public static void log(String msg) { + try { + final Crashlytics crashlytics = Crashlytics.getInstance(); + crashlytics.core.log(msg); + } catch (NullPointerException | IllegalStateException e) { + android.util.Log.d(TAG, "Ignoring opted out non-initialized log: " + msg); + } + } + + // Crashlytics log + public static void log(int priority, String tag, String msg) { + try { + final Crashlytics crashlytics = Crashlytics.getInstance(); + crashlytics.core.log(priority, tag, msg); + } catch (NullPointerException | IllegalStateException e) { + android.util.Log.d(TAG, "Ignoring opted out non-initialized log: " + msg); + } + } + + public static boolean fabricEnabled() { + return SP.getBoolean("enable_fabric", true); + } + + // Answers logCustom + public void logCustom(CustomEvent event) { + try { + final Answers answers = Answers.getInstance(); + if (fabricEnabled()) { + answers.logCustom(event); + } else { + android.util.Log.d(TAG, "Ignoring recently opted-out event: " + event.toString()); + } + } catch (NullPointerException | IllegalStateException e) { + android.util.Log.d(TAG, "Ignoring opted-out non-initialized event: " + event.toString()); + } + } + +} diff --git a/app/src/main/java/info/nightscout/utils/HardLimits.java b/app/src/main/java/info/nightscout/utils/HardLimits.java index ff92cf2e87..8e1a05db0d 100644 --- a/app/src/main/java/info/nightscout/utils/HardLimits.java +++ b/app/src/main/java/info/nightscout/utils/HardLimits.java @@ -1,5 +1,8 @@ package info.nightscout.utils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; @@ -8,17 +11,90 @@ import info.nightscout.androidaps.R; */ public class HardLimits { - final static double MAXBOLUS_ADULT = 17d; - final static double MAXBOLUS_TEENAGE = 10d; - final static double MAXBOLUS_CHILD = 5d; + private static Logger log = LoggerFactory.getLogger(HardLimits.class); - public static double maxBolus() { - String age = SP.getString(R.string.key_age, ""); + final static int CHILD = 0; + final static int TEENAGE = 1; + final static int ADULT = 2; + final static int RESISTANTADULT = 3; - if (age.equals(MainApp.sResources.getString(R.string.key_adult))) return MAXBOLUS_ADULT; - if (age.equals(MainApp.sResources.getString(R.string.key_teenage))) return MAXBOLUS_TEENAGE; - if (age.equals(MainApp.sResources.getString(R.string.key_child))) return MAXBOLUS_CHILD; - return MAXBOLUS_ADULT; + final static double[] MAXBOLUS = {5d, 10d, 17d, 25d}; + + // Very Hard Limits Ranges + // First value is the Lowest and second value is the Highest a Limit can define + public static final int[] VERY_HARD_LIMIT_MIN_BG = {72, 180}; + public static final int[] VERY_HARD_LIMIT_MAX_BG = {90, 270}; + public static final int[] VERY_HARD_LIMIT_TARGET_BG = {80, 200}; + // Very Hard Limits Ranges for Temp Targets + public static final int[] VERY_HARD_LIMIT_TEMP_MIN_BG = {72, 180}; + public static final int[] VERY_HARD_LIMIT_TEMP_MAX_BG = {72, 270}; + public static final int[] VERY_HARD_LIMIT_TEMP_TARGET_BG = {72, 200}; + + public static final double MINDIA = 2; + public static final double MAXDIA = 7; + + public static final double MINIC = 2; + public static final double MAXIC = 100; + + public static final double MINISF = 2; // mgdl + public static final double MAXISF = 720; // mgdl + + public static final double[] MAXIOB_AMA = {3, 5, 7, 12}; + public static final double[] MAXIOB_SMB = {3, 7, 12, 25}; + + public static final double[] MAXBASAL = {2, 5, 10, 12}; + + + private static int loadAge() { + String sp_age = SP.getString(R.string.key_age, ""); + int age; + + if (sp_age.equals(MainApp.gs(R.string.key_child))) + age = CHILD; + else if (sp_age.equals(MainApp.gs(R.string.key_teenage))) + age = TEENAGE; + else if (sp_age.equals(MainApp.gs(R.string.key_adult))) + age = ADULT; + else if (sp_age.equals(MainApp.gs(R.string.key_resistantadult))) + age = RESISTANTADULT; + else age = ADULT; + + return age; } + public static double maxBolus() { + return MAXBOLUS[loadAge()]; + } + + public static double maxIobAMA() { + return MAXIOB_AMA[loadAge()]; + } + + public static double maxIobSMB() { + return MAXIOB_SMB[loadAge()]; + } + + public static double maxBasal() { + return MAXBASAL[loadAge()]; + } + + // safety checks + public static boolean checkOnlyHardLimits(Double value, String valueName, double lowLimit, double highLimit) { + return value.equals(verifyHardLimits(value, valueName, lowLimit, highLimit)); + } + + public static Double verifyHardLimits(Double value, String valueName, double lowLimit, double highLimit) { + Double newvalue = value; + if (newvalue < lowLimit || newvalue > highLimit) { + newvalue = Math.max(newvalue, lowLimit); + newvalue = Math.min(newvalue, highLimit); + String msg = String.format(MainApp.gs(R.string.valueoutofrange), valueName); + msg += ".\n"; + msg += String.format(MainApp.gs(R.string.valuelimitedto), value, newvalue); + log.error(msg); + NSUpload.uploadError(msg); + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), msg, R.raw.error); + } + return newvalue; + } } diff --git a/app/src/main/java/info/nightscout/utils/JsonHelper.java b/app/src/main/java/info/nightscout/utils/JsonHelper.java new file mode 100644 index 0000000000..503d0bf395 --- /dev/null +++ b/app/src/main/java/info/nightscout/utils/JsonHelper.java @@ -0,0 +1,113 @@ +package info.nightscout.utils; + +import android.support.annotation.Nullable; + +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * JSonHelper is a Helper class which contains several methods to safely get data from the ggiven JSONObject. + * + * Created by triplem on 04.01.18. + */ + +public class JsonHelper { + + private static final Logger log = LoggerFactory.getLogger(JsonHelper.class); + + private JsonHelper() {}; + + public static Object safeGetObject(JSONObject json, String fieldName, Object defaultValue) { + Object result = defaultValue; + + if (json.has(fieldName)) { + try { + result = json.get(fieldName); + } catch (JSONException ignored) { + } + } + + return result; + } + + @Nullable + public static String safeGetString(JSONObject json, String fieldName) { + String result = null; + + if (json.has(fieldName)) { + try { + result = json.getString(fieldName); + } catch (JSONException ignored) { + } + } + + return result; + } + + public static String safeGetString(JSONObject json, String fieldName, String defaultValue) { + String result = defaultValue; + + if (json.has(fieldName)) { + try { + result = json.getString(fieldName); + } catch (JSONException ignored) { + } + } + + return result; + } + + public static double safeGetDouble(JSONObject json, String fieldName) { + double result = 0d; + + if (json.has(fieldName)) { + try { + result = json.getDouble(fieldName); + } catch (JSONException ignored) { + } + } + + return result; + } + + public static int safeGetInt(JSONObject json, String fieldName) { + int result = 0; + + if (json.has(fieldName)) { + try { + result = json.getInt(fieldName); + } catch (JSONException ignored) { + } + } + + return result; + } + + public static long safeGetLong(JSONObject json, String fieldName) { + long result = 0; + + if (json.has(fieldName)) { + try { + result = json.getLong(fieldName); + } catch (JSONException e) { + } + } + + return result; + } + + public static boolean safeGetBoolean(JSONObject json, String fieldName) { + boolean result = false; + + if (json.has(fieldName)) { + try { + result = json.getBoolean(fieldName); + } catch (JSONException e) { + } + } + + return result; + } +} diff --git a/app/src/main/java/info/nightscout/utils/LocalAlertUtils.java b/app/src/main/java/info/nightscout/utils/LocalAlertUtils.java index e74818d447..cc1a22355a 100644 --- a/app/src/main/java/info/nightscout/utils/LocalAlertUtils.java +++ b/app/src/main/java/info/nightscout/utils/LocalAlertUtils.java @@ -1,9 +1,7 @@ package info.nightscout.utils; -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Date; @@ -15,39 +13,48 @@ import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.DatabaseHelper; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.Loop.LoopPlugin; +import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.Overview.notifications.Notification; -import info.nightscout.androidaps.receivers.KeepAliveReceiver; /** * Created by adrian on 17/12/17. */ public class LocalAlertUtils { - public static int missedReadingsThreshold() { - return SP.getInt(MainApp.sResources.getString(R.string.key_missed_bg_readings_threshold), 30) * 60 * 1000; + private static Logger log = LoggerFactory.getLogger(LocalAlertUtils.class); + + public static long missedReadingsThreshold() { + return T.mins(SP.getInt(MainApp.sResources.getString(R.string.key_missed_bg_readings_threshold), 30)).msecs(); } - private static int pumpUnreachableThreshold() { - return SP.getInt(MainApp.sResources.getString(R.string.key_pump_unreachable_threshold), 30) * 60 * 1000; + private static long pumpUnreachableThreshold() { + return T.mins(SP.getInt(MainApp.sResources.getString(R.string.key_pump_unreachable_threshold), 30)).msecs(); } public static void checkPumpUnreachableAlarm(Date lastConnection, boolean isStatusOutdated) { boolean alarmTimeoutExpired = lastConnection.getTime() + pumpUnreachableThreshold() < System.currentTimeMillis(); - boolean nextAlarmOccurrenceReached = SP.getLong("nextPumpDisconnectedAlarm", 0l) < System.currentTimeMillis(); + boolean nextAlarmOccurrenceReached = SP.getLong("nextPumpDisconnectedAlarm", 0L) < System.currentTimeMillis(); if (Config.APS && SP.getBoolean(MainApp.sResources.getString(R.string.key_enable_pump_unreachable_alert), true) - && isStatusOutdated && alarmTimeoutExpired && nextAlarmOccurrenceReached && !ConfigBuilderPlugin.getActiveLoop().isDisconnected()) { + && isStatusOutdated && alarmTimeoutExpired && nextAlarmOccurrenceReached && !LoopPlugin.getPlugin().isDisconnected()) { + log.debug("Generating pump unreachable alarm. lastConnection: " + DateUtil.dateAndTimeString(lastConnection) + " isStatusOutdated: " + isStatusOutdated); Notification n = new Notification(Notification.PUMP_UNREACHABLE, MainApp.sResources.getString(R.string.pump_unreachable), Notification.URGENT); n.soundId = R.raw.alarm; SP.putLong("nextPumpDisconnectedAlarm", System.currentTimeMillis() + pumpUnreachableThreshold()); MainApp.bus().post(new EventNewNotification(n)); + if (SP.getBoolean(R.string.key_ns_create_announcements_from_errors, true)) { + NSUpload.uploadError(n.text); + } } + if (!isStatusOutdated && !alarmTimeoutExpired) + MainApp.bus().post(new EventDismissNotification(Notification.PUMP_UNREACHABLE)); } /*Presnoozes the alarms with 5 minutes if no snooze exists. - * Call only at startup! - */ + * Call only at startup! + */ public static void presnoozeAlarms() { if (SP.getLong("nextMissedReadingsAlarm", 0l) < System.currentTimeMillis()) { SP.putLong("nextMissedReadingsAlarm", System.currentTimeMillis() + 5 * 60 * 1000); @@ -68,12 +75,12 @@ public class LocalAlertUtils { SP.putLong("nextPumpDisconnectedAlarm", nextPumpDisconnectedAlarm); } - public static void notifyPumpStatusRead(){ + public static void notifyPumpStatusRead() { //TODO: persist the actual time the pump is read and simplify the whole logic when to alarm final PumpInterface pump = ConfigBuilderPlugin.getActivePump(); final Profile profile = MainApp.getConfigBuilder().getProfile(); - if (pump != null && profile != null && profile.getBasal() != null) { + if (pump != null && profile != null) { Date lastConnection = pump.lastDataTime(); long earliestAlarmTime = lastConnection.getTime() + pumpUnreachableThreshold(); if (SP.getLong("nextPumpDisconnectedAlarm", 0l) < earliestAlarmTime) { @@ -91,6 +98,9 @@ public class LocalAlertUtils { n.soundId = R.raw.alarm; SP.putLong("nextMissedReadingsAlarm", System.currentTimeMillis() + missedReadingsThreshold()); MainApp.bus().post(new EventNewNotification(n)); + if (SP.getBoolean(R.string.key_ns_create_announcements_from_errors, true)) { + NSUpload.uploadError(n.text); + } } } } diff --git a/app/src/main/java/info/nightscout/utils/LogDialog.java b/app/src/main/java/info/nightscout/utils/LogDialog.java index 0bd4216163..b98582c05f 100644 --- a/app/src/main/java/info/nightscout/utils/LogDialog.java +++ b/app/src/main/java/info/nightscout/utils/LogDialog.java @@ -7,7 +7,6 @@ import android.content.DialogInterface; import android.content.ClipboardManager; import android.widget.TextView; -import com.crashlytics.android.Crashlytics; import java.io.BufferedReader; import java.io.IOException; diff --git a/app/src/main/java/info/nightscout/utils/NSUpload.java b/app/src/main/java/info/nightscout/utils/NSUpload.java index 2ec3a57dd6..3cd252f19a 100644 --- a/app/src/main/java/info/nightscout/utils/NSUpload.java +++ b/app/src/main/java/info/nightscout/utils/NSUpload.java @@ -7,7 +7,9 @@ import android.content.pm.ResolveInfo; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; +import android.support.annotation.Nullable; +import org.apache.commons.lang3.StringUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -28,14 +30,13 @@ import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.CareportalEvent; import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.ProfileSwitch; +import info.nightscout.androidaps.db.TempTarget; import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Loop.APSResult; import info.nightscout.androidaps.plugins.Loop.DeviceStatus; import info.nightscout.androidaps.plugins.Loop.LoopPlugin; import info.nightscout.androidaps.plugins.NSClientInternal.data.DbLogger; -import info.nightscout.androidaps.plugins.OpenAPSAMA.DetermineBasalResultAMA; -import info.nightscout.androidaps.plugins.OpenAPSMA.DetermineBasalResultMA; /** * Created by mike on 26.05.2017. @@ -142,7 +143,7 @@ public class NSUpload { data.put("splitNow", 0); data.put("splitExt", 100); data.put("enteredinsulin", extendedBolus.insulin); - data.put("relative", extendedBolus.insulin); + data.put("relative", extendedBolus.insulin / extendedBolus.durationInMinutes * 60); // U/h if (extendedBolus.pumpId != 0) data.put("pumpId", extendedBolus.pumpId); data.put("created_at", DateUtil.toISOString(extendedBolus.date)); @@ -190,6 +191,14 @@ public class NSUpload { } public static void uploadDeviceStatus() { + Profile profile = MainApp.getConfigBuilder().getProfile(); + String profileName = MainApp.getConfigBuilder().getProfileName(); + + if (profile == null || profileName == null) { + log.error("Profile is null. Skipping upload"); + return; + } + DeviceStatus deviceStatus = new DeviceStatus(); try { LoopPlugin.LastRun lastRun = LoopPlugin.lastRun; @@ -199,34 +208,34 @@ public class NSUpload { apsResult.json().put("timestamp", DateUtil.toISOString(lastRun.lastAPSRun)); deviceStatus.suggested = apsResult.json(); - if (lastRun.request instanceof DetermineBasalResultMA) { - DetermineBasalResultMA result = (DetermineBasalResultMA) lastRun.request; - deviceStatus.iob = result.iob.json(); - deviceStatus.iob.put("time", DateUtil.toISOString(lastRun.lastAPSRun)); - } + deviceStatus.iob = lastRun.request.iob.json(); + deviceStatus.iob.put("time", DateUtil.toISOString(lastRun.lastAPSRun)); - if (lastRun.request instanceof DetermineBasalResultAMA) { - DetermineBasalResultAMA result = (DetermineBasalResultAMA) lastRun.request; - deviceStatus.iob = result.iob.json(); - deviceStatus.iob.put("time", DateUtil.toISOString(lastRun.lastAPSRun)); - } + JSONObject requested = new JSONObject(); - if (lastRun.setByPump != null && lastRun.setByPump.enacted) { // enacted + if (lastRun.tbrSetByPump != null && lastRun.tbrSetByPump.enacted) { // enacted deviceStatus.enacted = lastRun.request.json(); - deviceStatus.enacted.put("rate", lastRun.setByPump.json().get("rate")); - deviceStatus.enacted.put("duration", lastRun.setByPump.json().get("duration")); + deviceStatus.enacted.put("rate", lastRun.tbrSetByPump.json(profile).get("rate")); + deviceStatus.enacted.put("duration", lastRun.tbrSetByPump.json(profile).get("duration")); deviceStatus.enacted.put("recieved", true); - JSONObject requested = new JSONObject(); requested.put("duration", lastRun.request.duration); requested.put("rate", lastRun.request.rate); requested.put("temp", "absolute"); deviceStatus.enacted.put("requested", requested); } + if (lastRun.smbSetByPump != null && lastRun.smbSetByPump.enacted) { // enacted + if (deviceStatus.enacted == null) { + deviceStatus.enacted = lastRun.request.json(); + } + deviceStatus.enacted.put("smb", lastRun.smbSetByPump.bolusDelivered); + requested.put("smb", lastRun.request.smb); + deviceStatus.enacted.put("requested", requested); + } } else { log.debug("OpenAPS data too old to upload"); } deviceStatus.device = "openaps://" + Build.MANUFACTURER + " " + Build.MODEL; - JSONObject pumpstatus = ConfigBuilderPlugin.getActivePump().getJSONStatus(); + JSONObject pumpstatus = ConfigBuilderPlugin.getActivePump().getJSONStatus(profile, profileName); if (pumpstatus != null) { deviceStatus.pump = pumpstatus; } @@ -250,7 +259,7 @@ public class NSUpload { } } - public static void uploadBolusWizardRecord(DetailedBolusInfo detailedBolusInfo) { + public static void uploadTreatmentRecord(DetailedBolusInfo detailedBolusInfo) { JSONObject data = new JSONObject(); try { data.put("eventType", detailedBolusInfo.eventType); @@ -269,6 +278,9 @@ public class NSUpload { data.put("boluscalc", detailedBolusInfo.boluscalc); if (detailedBolusInfo.carbTime != 0) data.put("preBolus", detailedBolusInfo.carbTime); + if (!StringUtils.isEmpty(detailedBolusInfo.notes)) { + data.put("notes", detailedBolusInfo.notes); + } } catch (JSONException e) { log.error("Unhandled exception", e); } @@ -296,6 +308,30 @@ public class NSUpload { } } + public static void uploadTempTarget(TempTarget tempTarget) { + try { + Profile profile = MainApp.getConfigBuilder().getProfile(); + + if (profile == null) { + log.error("Profile is null. Skipping upload"); + return; + } + + JSONObject data = new JSONObject(); + data.put("eventType", CareportalEvent.TEMPORARYTARGET); + data.put("duration", tempTarget.durationInMinutes); + data.put("reason", tempTarget.reason); + data.put("targetBottom", Profile.fromMgdlToUnits(tempTarget.low, profile.getUnits())); + data.put("targetTop", Profile.fromMgdlToUnits(tempTarget.high, profile.getUnits())); + data.put("created_at", DateUtil.toISOString(tempTarget.date)); + data.put("units", profile.getUnits()); + data.put("enteredBy", MainApp.instance().getString(R.string.app_name)); + uploadCareportalEntryToNS(data); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + } + public static void updateProfileSwitch(ProfileSwitch profileSwitch) { try { JSONObject data = new JSONObject(); @@ -400,6 +436,10 @@ public class NSUpload { } public static void uploadError(String error) { + uploadError(error, new Date()); + } + + public static void uploadError(String error, Date date) { Context context = MainApp.instance().getApplicationContext(); Bundle bundle = new Bundle(); bundle.putString("action", "dbAdd"); @@ -407,7 +447,8 @@ public class NSUpload { JSONObject data = new JSONObject(); try { data.put("eventType", "Announcement"); - data.put("created_at", DateUtil.toISOString(new Date())); + data.put("created_at", DateUtil.toISOString(date)); + data.put("enteredBy", SP.getString("careportal_enteredby", MainApp.gs(R.string.app_name))); data.put("notes", error); data.put("isAnnouncement", true); } catch (JSONException e) { @@ -455,7 +496,7 @@ public class NSUpload { try { data.put("eventType", "Note"); data.put("created_at", DateUtil.toISOString(new Date())); - data.put("notes", MainApp.sResources.getString(R.string.androidaps_start)); + data.put("notes", MainApp.sResources.getString(R.string.androidaps_start)+" - "+ Build.MANUFACTURER + " "+ Build.MODEL); } catch (JSONException e) { log.error("Unhandled exception", e); } @@ -465,7 +506,31 @@ public class NSUpload { intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); context.sendBroadcast(intent); DbLogger.dbAdd(intent, data.toString()); + } + } + + public static void uploadEvent(String careportalEvent, long time, @Nullable String notes) { + Context context = MainApp.instance().getApplicationContext(); + Bundle bundle = new Bundle(); + bundle.putString("action", "dbAdd"); + bundle.putString("collection", "treatments"); + JSONObject data = new JSONObject(); + try { + data.put("eventType", careportalEvent); + data.put("created_at", DateUtil.toISOString(time)); + data.put("enteredBy", SP.getString("careportal_enteredby", MainApp.gs(R.string.app_name))); + if (notes != null) { + data.put("notes", notes); + } + } catch (JSONException e) { + log.error("Unhandled exception", e); } + bundle.putString("data", data.toString()); + Intent intent = new Intent(Intents.ACTION_DATABASE); + intent.putExtras(bundle); + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + context.sendBroadcast(intent); + DbLogger.dbAdd(intent, data.toString()); } public static void removeFoodFromNS(String _id) { diff --git a/app/src/main/java/info/nightscout/utils/NumberPicker.java b/app/src/main/java/info/nightscout/utils/NumberPicker.java index 53ffd90f7b..64239cbaee 100644 --- a/app/src/main/java/info/nightscout/utils/NumberPicker.java +++ b/app/src/main/java/info/nightscout/utils/NumberPicker.java @@ -5,6 +5,7 @@ import android.os.Handler; import android.os.Message; import android.text.Editable; import android.text.TextWatcher; +import android.text.method.DigitsKeyListener; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -148,6 +149,9 @@ public class NumberPicker extends LinearLayout implements View.OnKeyListener, } public void setParams(Double initValue, Double minValue, Double maxValue, Double step, NumberFormat formater, boolean allowZero, TextWatcher textWatcher) { + if (this.textWatcher != null) { + editText.removeTextChangedListener(this.textWatcher); + } setParams(initValue, minValue, maxValue, step, formater, allowZero); this.textWatcher = textWatcher; editText.addTextChangedListener(textWatcher); @@ -161,6 +165,8 @@ public class NumberPicker extends LinearLayout implements View.OnKeyListener, this.formater = formater; this.allowZero = allowZero; + editText.setKeyListener(DigitsKeyListener.getInstance(minValue < 0, step != Math.rint(step))); + if (textWatcher != null) editText.removeTextChangedListener(textWatcher); updateEditText(); @@ -199,7 +205,7 @@ public class NumberPicker extends LinearLayout implements View.OnKeyListener, updateEditText(); } - private void dec( int multiplier) { + private void dec(int multiplier) { value -= step * multiplier; if (value < minValue) { value = minValue; diff --git a/app/src/main/java/info/nightscout/utils/OKDialog.java b/app/src/main/java/info/nightscout/utils/OKDialog.java index 6bd84cbe71..3212e6b1fd 100644 --- a/app/src/main/java/info/nightscout/utils/OKDialog.java +++ b/app/src/main/java/info/nightscout/utils/OKDialog.java @@ -61,4 +61,19 @@ public class OKDialog { log.debug("show_dialog exception: " + e); } } + + public static void showConfirmation(final Activity activity, String message, final Runnable runnable) { + AlertDialog alertDialog = new AlertDialog.Builder(new ContextThemeWrapper(activity, R.style.AppTheme)) + .setMessage(message) + .setPositiveButton(android.R.string.ok, (dialog, which) -> { + dialog.dismiss(); + if (runnable != null) { + SystemClock.sleep(100); + activity.runOnUiThread(runnable); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + } diff --git a/app/src/main/java/info/nightscout/utils/SP.java b/app/src/main/java/info/nightscout/utils/SP.java index 16015d8956..23a95840a8 100644 --- a/app/src/main/java/info/nightscout/utils/SP.java +++ b/app/src/main/java/info/nightscout/utils/SP.java @@ -24,7 +24,7 @@ public class SP { return sharedPreferences.getString(key, defaultValue); } - static public boolean getBoolean(int resourceID, boolean defaultValue) { + static public boolean getBoolean(int resourceID, Boolean defaultValue) { try { return sharedPreferences.getBoolean(MainApp.sResources.getString(resourceID), defaultValue); } catch (Exception e) { @@ -32,7 +32,7 @@ public class SP { } } - static public boolean getBoolean(String key, boolean defaultValue) { + static public boolean getBoolean(String key, Boolean defaultValue) { try { return sharedPreferences.getBoolean(key, defaultValue); } catch (Exception e) { diff --git a/app/src/main/java/info/nightscout/utils/T.java b/app/src/main/java/info/nightscout/utils/T.java new file mode 100644 index 0000000000..ce8541dd8c --- /dev/null +++ b/app/src/main/java/info/nightscout/utils/T.java @@ -0,0 +1,59 @@ +package info.nightscout.utils; + +/** + * Created by mike on 26.03.2018. + */ + +public class T { + private long time; // in msec + + public static T msecs(long msec) { + T t = new T(); + t.time = msec; + return t; + } + + public static T secs(long sec) { + T t = new T(); + t.time = sec * 1000L; + return t; + } + + public static T mins(long min) { + T t = new T(); + t.time = min * 60 * 1000L; + return t; + } + + public static T hours(long hour) { + T t = new T(); + t.time = hour * 60 * 60 * 1000L; + return t; + } + + public static T days(long day) { + T t = new T(); + t.time = day * 24 * 60 * 60 * 1000L; + return t; + } + + public long msecs() { + return time; + } + + public long secs() { + return time / 1000L; + } + + public long mins() { + return time / 60 / 1000L; + } + + public long hours() { + return time / 60 / 60 / 1000L; + } + + public long days() { + return time / 24 / 60 / 60 / 1000L; + } +} diff --git a/app/src/main/java/info/nightscout/utils/TimeListEdit.java b/app/src/main/java/info/nightscout/utils/TimeListEdit.java index bc41ece59d..ec03734b18 100644 --- a/app/src/main/java/info/nightscout/utils/TimeListEdit.java +++ b/app/src/main/java/info/nightscout/utils/TimeListEdit.java @@ -78,6 +78,7 @@ public class TimeListEdit { private void buildView() { layout = (LinearLayout) view.findViewById(resLayoutId); + layout.removeAllViews(); textlabel = new TextView(context); textlabel.setText(label); diff --git a/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/Menu.java b/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/Menu.java new file mode 100644 index 0000000000..cd3b7be770 --- /dev/null +++ b/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/Menu.java @@ -0,0 +1,142 @@ +package org.monkey.d.ruffy.ruffy.driver.display; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import org.monkey.d.ruffy.ruffy.driver.display.menu.BolusType; +import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuBlink; +import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuDate; +import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Created by fishermen21 on 20.05.17. + */ + +public class Menu implements Parcelable{ + private MenuType type; + private Map attributes = new HashMap<>(); + + public Menu(MenuType type) + { + this.type = type; + } + + public Menu(Parcel in) { + this.type = MenuType.valueOf(in.readString()); + while(in.dataAvail()>0) { + try { + String attr = in.readString(); + String clas = in.readString(); + String value = in.readString(); + + if(attr!=null && clas!=null && value!=null) { + MenuAttribute a = MenuAttribute.valueOf(attr); + Object o = null; + if (Integer.class.toString().equals(clas)) { + o = new Integer(value); + } else if (Double.class.toString().equals(clas)) { + o = new Double(value); + } else if (Boolean.class.toString().equals(clas)) { + o = new Boolean(value); + } else if (MenuDate.class.toString().equals(clas)) { + o = new MenuDate(value); + } else if (MenuTime.class.toString().equals(clas)) { + o = new MenuTime(value); + } else if (MenuBlink.class.toString().equals(clas)) { + o = new MenuBlink(); + } else if (BolusType.class.toString().equals(clas)) { + o = BolusType.valueOf(value); + } else if (String.class.toString().equals(clas)) { + o = new String(value); + } + + if (o != null) { + attributes.put(a, o); + } else { + Log.e("MenuIn", "failed to parse: " + attr + " / " + clas + " / " + value); + } + } + }catch(Exception e) + { + Log.e("MenuIn","Exception in read",e); + } + + } + } + + public void setAttribute(MenuAttribute key, Object value) + { + attributes.put(key,value); + } + + public List attributes() + { + return new LinkedList(attributes.keySet()); + } + + public Object getAttribute(MenuAttribute key) + { + return attributes.get(key); + } + + public MenuType getType() { + return type; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(type.toString()); + for(MenuAttribute a : attributes.keySet()) + { + try + { + + String atr = a.toString(); + Object o = attributes.get(a); + String clas = o.getClass().toString(); + String v = o.toString(); + if(atr != null && o != null && v != null) { + dest.writeString(atr); + dest.writeString(clas); + dest.writeString(v); + } + else + { + Log.e("Menu","null in write :/"); + } + }catch(Exception e) + { + Log.v("MenuOut","error in write",e); + } + } + } + public static final Parcelable.Creator

CREATOR = new + Parcelable.Creator() { + public Menu createFromParcel(Parcel in) { + return new Menu(in); + } + + public Menu[] newArray(int size) { + return new Menu[size]; + } + }; + + @Override + public String toString() { + return "Menu{" + + "type=" + type + + ", attributes=" + attributes + + '}'; + } +} diff --git a/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/MenuAttribute.java b/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/MenuAttribute.java new file mode 100644 index 0000000000..0404c89011 --- /dev/null +++ b/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/MenuAttribute.java @@ -0,0 +1,34 @@ +package org.monkey.d.ruffy.ruffy.driver.display; + +/** + * Created by fishermen21 on 22.05.17. + */ + +public enum MenuAttribute { + RUNTIME,//runtime of current operation, remaining time on main menu + BOLUS,//double units + BOLUS_REMAINING,//double units remain from current bolus + TBR,//double 0-500% + BASAL_RATE,//double units/h + BASAL_SELECTED,//int selected basal profile + BATTERY_STATE,//int, like insulin state + INSULIN_STATE,//int insulin warning 0 == no warning, 1== low, 2 == empty + LOCK_STATE,//int keylock state 0==no lock, 1==unlocked, 2==locked + MULTIWAVE_BOLUS,//double immediate bolus on multiwave + BOLUS_TYPE,//BolusType, only history uses MULTIWAVE + TIME,//time MenuTime + REMAINING_INSULIN,//double units + DATE,//date MenuDate + CURRENT_RECORD,//int current record + TOTAL_RECORD, //int total num record + ERROR, //int errorcode + WARNING, //int errorcode + MESSAGE, //string errormessage + DAILY_TOTAL, //double units + BASAL_TOTAL, //double total basal + BASAL_START, //time MenuTime the basalrate starts + BASAL_END, // time MenuTime the basalrate ends + DEBUG_TIMING, //double with timing infos + WARANTY, //boolean true if out of waranty + ERROR_OR_WARNING, // set if menu in blink during error/warning +} diff --git a/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/MenuType.java b/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/MenuType.java new file mode 100644 index 0000000000..4d00ea9155 --- /dev/null +++ b/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/MenuType.java @@ -0,0 +1,42 @@ +package org.monkey.d.ruffy.ruffy.driver.display; + +/** + * Created by fishermen21 on 22.05.17. + */ + +public enum MenuType { + MAIN_MENU, + STOP_MENU, + BOLUS_MENU, + BOLUS_ENTER, + EXTENDED_BOLUS_MENU, + BOLUS_DURATION, + MULTIWAVE_BOLUS_MENU, + IMMEDIATE_BOLUS, + TBR_MENU, + MY_DATA_MENU, + BASAL_MENU, + BASAL_1_MENU, + BASAL_2_MENU, + BASAL_3_MENU, + BASAL_4_MENU, + BASAL_5_MENU, + DATE_AND_TIME_MENU, + ALARM_MENU, + MENU_SETTINGS_MENU, + BLUETOOTH_MENU, + THERAPY_MENU, + PUMP_MENU, + QUICK_INFO, + BOLUS_DATA, + DAILY_DATA, + TBR_DATA, + ERROR_DATA, + TBR_SET, + TBR_DURATION, + STOP, + START_MENU, + BASAL_TOTAL, + BASAL_SET, + WARNING_OR_ERROR, +} diff --git a/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/menu/BolusType.java b/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/menu/BolusType.java new file mode 100644 index 0000000000..82572acc93 --- /dev/null +++ b/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/menu/BolusType.java @@ -0,0 +1,13 @@ +package org.monkey.d.ruffy.ruffy.driver.display.menu; + +/** + * Created by fishermen21 on 22.05.17. + */ + +public enum BolusType{ + NORMAL, + EXTENDED, + MULTIWAVE, + MULTIWAVE_BOLUS, + MULTIWAVE_EXTENDED, +} diff --git a/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/menu/MenuBlink.java b/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/menu/MenuBlink.java new file mode 100644 index 0000000000..18c0374dcb --- /dev/null +++ b/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/menu/MenuBlink.java @@ -0,0 +1,12 @@ +package org.monkey.d.ruffy.ruffy.driver.display.menu; + +/** + * Created by fishermen21 on 22.05.17. + */ + +public class MenuBlink { + @Override + public String toString() { + return "BLINK"; + } +} diff --git a/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/menu/MenuDate.java b/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/menu/MenuDate.java new file mode 100644 index 0000000000..05cc741413 --- /dev/null +++ b/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/menu/MenuDate.java @@ -0,0 +1,35 @@ +package org.monkey.d.ruffy.ruffy.driver.display.menu; + +/** + * Created by fishermen21 on 24.05.17. + */ + +public class MenuDate { + private final int day; + private final int month; + + + public MenuDate(int day, int month) { + this.day = day; + this.month = month; + } + + public MenuDate(String value) { + String[] p = value.split("\\."); + day = Integer.parseInt(p[0]); + month = Integer.parseInt(p[1]); + } + + public int getDay() { + return day; + } + + public int getMonth() { + return month; + } + + @Override + public String toString() { + return day+"."+String.format("%02d",month)+"."; + } +} diff --git a/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/menu/MenuTime.java b/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/menu/MenuTime.java new file mode 100644 index 0000000000..147aafc8eb --- /dev/null +++ b/app/src/main/java/org/monkey/d/ruffy/ruffy/driver/display/menu/MenuTime.java @@ -0,0 +1,36 @@ +package org.monkey.d.ruffy.ruffy.driver.display.menu; + +/** + * Created by fishermen21 on 22.05.17. + */ + +public class MenuTime { + + private final int hour; + private final int minute; + + public MenuTime(int hour, int minute) + { + this.hour = hour; + this.minute = minute; + } + + public MenuTime(String value) { + String[] p = value.split(":"); + hour = Integer.parseInt(p[0]); + minute = Integer.parseInt(p[1]); + } + + public int getHour() { + return hour; + } + + public int getMinute() { + return minute; + } + + @Override + public String toString() { + return hour+":"+String.format("%02d",minute); + } +} diff --git a/app/src/main/jniLibs/arm64-v8a/libBleCommandUtil.so b/app/src/main/jniLibs/arm64-v8a/libBleCommandUtil.so deleted file mode 100644 index 69e283b5fe..0000000000 Binary files a/app/src/main/jniLibs/arm64-v8a/libBleCommandUtil.so and /dev/null differ diff --git a/app/src/main/jniLibs/armeabi-v7a/libBleCommandUtil.so b/app/src/main/jniLibs/armeabi-v7a/libBleCommandUtil.so deleted file mode 100644 index 0c717bc3e8..0000000000 Binary files a/app/src/main/jniLibs/armeabi-v7a/libBleCommandUtil.so and /dev/null differ diff --git a/app/src/main/jniLibs/armeabi/libBleCommandUtil.so b/app/src/main/jniLibs/armeabi/libBleCommandUtil.so deleted file mode 100644 index a51a8c7d9a..0000000000 Binary files a/app/src/main/jniLibs/armeabi/libBleCommandUtil.so and /dev/null differ diff --git a/app/src/main/jniLibs/mips/libBleCommandUtil.so b/app/src/main/jniLibs/mips/libBleCommandUtil.so deleted file mode 100644 index fcff5eb6b2..0000000000 Binary files a/app/src/main/jniLibs/mips/libBleCommandUtil.so and /dev/null differ diff --git a/app/src/main/jniLibs/mips64/libBleCommandUtil.so b/app/src/main/jniLibs/mips64/libBleCommandUtil.so deleted file mode 100644 index a8a292ea13..0000000000 Binary files a/app/src/main/jniLibs/mips64/libBleCommandUtil.so and /dev/null differ diff --git a/app/src/main/jniLibs/x86/libBleCommandUtil.so b/app/src/main/jniLibs/x86/libBleCommandUtil.so deleted file mode 100644 index 638a9def95..0000000000 Binary files a/app/src/main/jniLibs/x86/libBleCommandUtil.so and /dev/null differ diff --git a/app/src/main/jniLibs/x86_64/libBleCommandUtil.so b/app/src/main/jniLibs/x86_64/libBleCommandUtil.so deleted file mode 100644 index 94873d3732..0000000000 Binary files a/app/src/main/jniLibs/x86_64/libBleCommandUtil.so and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_add.png b/app/src/main/res/drawable-hdpi/ic_action_add.png deleted file mode 100644 index ca13239a7d..0000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_action_add.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_minus.png b/app/src/main/res/drawable-hdpi/ic_action_minus.png deleted file mode 100644 index e68aed5a2f..0000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_action_minus.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/icon_home_target.png b/app/src/main/res/drawable-hdpi/icon_home_target.png deleted file mode 100644 index 0b2f2b166a..0000000000 Binary files a/app/src/main/res/drawable-hdpi/icon_home_target.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/icon_local_activate.png b/app/src/main/res/drawable-hdpi/icon_local_activate.png new file mode 100644 index 0000000000..4ef567ffb6 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/icon_local_activate.png differ diff --git a/app/src/main/res/drawable-hdpi/icon_local_reset.png b/app/src/main/res/drawable-hdpi/icon_local_reset.png new file mode 100644 index 0000000000..7d38cf0b50 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/icon_local_reset.png differ diff --git a/app/src/main/res/drawable-hdpi/icon_local_save.png b/app/src/main/res/drawable-hdpi/icon_local_save.png new file mode 100644 index 0000000000..e5c1d5cedc Binary files /dev/null and b/app/src/main/res/drawable-hdpi/icon_local_save.png differ diff --git a/app/src/main/res/drawable-hdpi/icon_xdrip.png b/app/src/main/res/drawable-hdpi/icon_xdrip.png new file mode 100644 index 0000000000..bda51738e7 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/icon_xdrip.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_add.png b/app/src/main/res/drawable-mdpi/ic_action_add.png deleted file mode 100644 index 06ff3a5c05..0000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_action_add.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_minus.png b/app/src/main/res/drawable-mdpi/ic_action_minus.png deleted file mode 100644 index 9367de01d7..0000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_action_minus.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/icon_home_target.png b/app/src/main/res/drawable-mdpi/icon_home_target.png deleted file mode 100644 index 9fc7086e9d..0000000000 Binary files a/app/src/main/res/drawable-mdpi/icon_home_target.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/icon_local_activate.png b/app/src/main/res/drawable-mdpi/icon_local_activate.png new file mode 100644 index 0000000000..b39c72b407 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/icon_local_activate.png differ diff --git a/app/src/main/res/drawable-mdpi/icon_local_reset.png b/app/src/main/res/drawable-mdpi/icon_local_reset.png new file mode 100644 index 0000000000..ad2e0eaf6d Binary files /dev/null and b/app/src/main/res/drawable-mdpi/icon_local_reset.png differ diff --git a/app/src/main/res/drawable-mdpi/icon_local_save.png b/app/src/main/res/drawable-mdpi/icon_local_save.png new file mode 100644 index 0000000000..843beed523 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/icon_local_save.png differ diff --git a/app/src/main/res/drawable-mdpi/icon_xdrip.png b/app/src/main/res/drawable-mdpi/icon_xdrip.png new file mode 100644 index 0000000000..700e274f5b Binary files /dev/null and b/app/src/main/res/drawable-mdpi/icon_xdrip.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_add.png b/app/src/main/res/drawable-xhdpi/ic_action_add.png deleted file mode 100644 index e5dd67bb44..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_action_add.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_minus.png b/app/src/main/res/drawable-xhdpi/ic_action_minus.png deleted file mode 100644 index 270dbe95a2..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_action_minus.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/icon_home_target.png b/app/src/main/res/drawable-xhdpi/icon_home_target.png deleted file mode 100644 index 545ff5db79..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/icon_home_target.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/icon_local_activate.png b/app/src/main/res/drawable-xhdpi/icon_local_activate.png new file mode 100644 index 0000000000..75e9af38ed Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/icon_local_activate.png differ diff --git a/app/src/main/res/drawable-xhdpi/icon_local_reset.png b/app/src/main/res/drawable-xhdpi/icon_local_reset.png new file mode 100644 index 0000000000..2813cb448d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/icon_local_reset.png differ diff --git a/app/src/main/res/drawable-xhdpi/icon_local_save.png b/app/src/main/res/drawable-xhdpi/icon_local_save.png new file mode 100644 index 0000000000..0cf81430cc Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/icon_local_save.png differ diff --git a/app/src/main/res/drawable-xhdpi/icon_xdrip.png b/app/src/main/res/drawable-xhdpi/icon_xdrip.png new file mode 100644 index 0000000000..ae4c77b1a8 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/icon_xdrip.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_add.png b/app/src/main/res/drawable-xxhdpi/ic_action_add.png deleted file mode 100644 index ed6a26cd87..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_action_add.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_minus.png b/app/src/main/res/drawable-xxhdpi/ic_action_minus.png deleted file mode 100644 index c2c7a8895f..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_action_minus.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/icon_home_target.png b/app/src/main/res/drawable-xxhdpi/icon_home_target.png deleted file mode 100644 index 70fd9ebc27..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/icon_home_target.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/icon_local_activate.png b/app/src/main/res/drawable-xxhdpi/icon_local_activate.png new file mode 100644 index 0000000000..600e6ee61c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/icon_local_activate.png differ diff --git a/app/src/main/res/drawable-xxhdpi/icon_local_reset.png b/app/src/main/res/drawable-xxhdpi/icon_local_reset.png new file mode 100644 index 0000000000..dc9d659dd7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/icon_local_reset.png differ diff --git a/app/src/main/res/drawable-xxhdpi/icon_local_save.png b/app/src/main/res/drawable-xxhdpi/icon_local_save.png new file mode 100644 index 0000000000..20448e4bf9 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/icon_local_save.png differ diff --git a/app/src/main/res/drawable-xxhdpi/icon_xdrip.png b/app/src/main/res/drawable-xxhdpi/icon_xdrip.png new file mode 100644 index 0000000000..c375417b1c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/icon_xdrip.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_add.png b/app/src/main/res/drawable-xxxhdpi/ic_action_add.png deleted file mode 100644 index 0d2d217356..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_action_add.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_minus.png b/app/src/main/res/drawable-xxxhdpi/ic_action_minus.png deleted file mode 100644 index 2af4e931e7..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_action_minus.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/icon_home_target.png b/app/src/main/res/drawable-xxxhdpi/icon_home_target.png deleted file mode 100644 index c1a4d99087..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/icon_home_target.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/icon_local_activate.png b/app/src/main/res/drawable-xxxhdpi/icon_local_activate.png new file mode 100644 index 0000000000..2de9ce9d4d Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/icon_local_activate.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/icon_local_reset.png b/app/src/main/res/drawable-xxxhdpi/icon_local_reset.png new file mode 100644 index 0000000000..ec54479149 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/icon_local_reset.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/icon_local_save.png b/app/src/main/res/drawable-xxxhdpi/icon_local_save.png new file mode 100644 index 0000000000..f2ecc45ac0 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/icon_local_save.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/icon_xdrip.png b/app/src/main/res/drawable-xxxhdpi/icon_xdrip.png new file mode 100644 index 0000000000..2b4b8323b8 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/icon_xdrip.png differ diff --git a/app/src/main/res/drawable/clone.png b/app/src/main/res/drawable/clone.png deleted file mode 100644 index 82fd90cca8..0000000000 Binary files a/app/src/main/res/drawable/clone.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_arrow_drop_down_white_24dp.xml b/app/src/main/res/drawable/ic_arrow_drop_down_white_24dp.xml new file mode 100644 index 0000000000..b82186765e --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_drop_down_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_drop_up_white_24dp.xml b/app/src/main/res/drawable/ic_arrow_drop_up_white_24dp.xml new file mode 100644 index 0000000000..c63c8c6529 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_drop_up_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_chevron_left_black_24dp.xml b/app/src/main/res/drawable/ic_chevron_left_black_24dp.xml new file mode 100644 index 0000000000..e6bb3ca920 --- /dev/null +++ b/app/src/main/res/drawable/ic_chevron_left_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_chevron_right_black_24dp.xml b/app/src/main/res/drawable/ic_chevron_right_black_24dp.xml new file mode 100644 index 0000000000..24835127dd --- /dev/null +++ b/app/src/main/res/drawable/ic_chevron_right_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_first_page_black_24dp.xml b/app/src/main/res/drawable/ic_first_page_black_24dp.xml new file mode 100644 index 0000000000..483f56c7c2 --- /dev/null +++ b/app/src/main/res/drawable/ic_first_page_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_last_page_black_24dp.xml b/app/src/main/res/drawable/ic_last_page_black_24dp.xml new file mode 100644 index 0000000000..0d04354c14 --- /dev/null +++ b/app/src/main/res/drawable/ic_last_page_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_home_target.png b/app/src/main/res/drawable/icon_home_target.png deleted file mode 100644 index 9fc7086e9d..0000000000 Binary files a/app/src/main/res/drawable/icon_home_target.png and /dev/null differ diff --git a/app/src/main/res/drawable/icon_insulin_carbs.xml b/app/src/main/res/drawable/icon_insulin_carbs.xml new file mode 100644 index 0000000000..6dcd511598 --- /dev/null +++ b/app/src/main/res/drawable/icon_insulin_carbs.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/actions_fill_dialog.xml b/app/src/main/res/layout/actions_fill_dialog.xml index 55e44f4bc7..2883485bf7 100644 --- a/app/src/main/res/layout/actions_fill_dialog.xml +++ b/app/src/main/res/layout/actions_fill_dialog.xml @@ -1,110 +1,153 @@ - + - + android:focusableInTouchMode="true" + android:orientation="vertical" + android:padding="10dp"> - + + + + + + + + + android:orientation="vertical"> - + - + + + + + + + android:paddingBottom="5dp" + android:paddingTop="5dp"> - + - + -